Skip to content
This repository has been archived by the owner on Dec 15, 2018. It is now read-only.

Bring back middleware :( #96

Merged
merged 3 commits into from
Oct 7, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 30 additions & 11 deletions ADVANCED.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,19 @@

Make sure to read http://redux.js.org/docs/recipes/ServerRendering.html to understand how the server/client Redux boilerplate works.

Here's what the setup looks like on the server:
Here's what the setup looks like on the server (assuming Node 4 LTS):

```js
const express = require('express');
const app = express();

const createStore = require('redux').createStore;
const routerForExpress = require('redux-little-router')
.routerForExpress;

const redux = require('redux');
const createStore = redux.createStore;
const compose = redux.compose;
const applyMiddleware = redux.applyMiddleware;

const routes = {
'/': {
'/whatever': {
Expand All @@ -24,19 +27,27 @@ const routes = {
};

app.use('/*', (req, res) => {
// Create the Redux store, passing in the
// Express request to the store enhancer.
// Create the Redux store, passing in the Express
// request to the routerForExpress factory.
//
// If you're using an Express sub-router,
// routerForExpress will infer the basename
// from req.baseUrl!
//
const router = routerForExpress({
routes,
request: req
})

const store = createStore(
state => state,
{ what: 'ever' },
routerForExpress({
routes,
request: req
})
compose(
router.routerEnhancer,
applyMiddleware(
router.routerMiddleware
)
)
);

// ...then renderToString() your components as usual,
Expand All @@ -51,7 +62,7 @@ app.use('/*', (req, res) => {
There's not much involved on the client side, post-server render:

```js
import { createStore } from 'redux';
import { createStore, compose, applyMiddleware } from 'redux';
import { routerForBrowser } from 'redux-little-router';

// The same routes that you used on the server.
Expand All @@ -64,10 +75,18 @@ const routes = {
}
};

const {
routerEnhancer,
routerMiddleware
} = routerForBrowser({ routes });

const store = createStore(
yourReducer,
window.__INITIAL_STATE,
routerForBrowser({ routes })
compose(
routerEnhancer,
applyMiddleware(routerMiddleware)
)
);

// ...then render() your components as usual,
Expand Down
26 changes: 18 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ While React Router is a great, well-supported library, it hoards URL state withi

## Redux usage

To hook into Redux applications, `redux-little-router` uses a store enhancer that wraps the `history` module and adds current and previous router state to your store. The enhancer listens for location changes and dispatches rich actions containing the URL, parameters, and any custom data assigned to the route. It also intercepts navigation actions and calls their equivalent method in `history`.
To hook into Redux applications, `redux-little-router` uses a store enhancer that wraps the `history` module and adds current and previous router state to your store. The enhancer listens for location changes and dispatches rich actions containing the URL, parameters, and any custom data assigned to the route. `redux-little-router` also adds a middleware that intercepts navigation actions and calls their equivalent method in `history`.

### Wiring up the boilerplate

Expand Down Expand Up @@ -67,16 +67,26 @@ const routes = {
}
};

// Install the router into the store for a browser-only environment
// Install the router into the store for a browser-only environment.
// routerForBrowser is a factory method that returns a store
// enhancer and a middleware.
const {
routerEnhancer,
routerMiddleware
} = routerForBrowser({
// The configured routes. Required.
routes,
// The basename for all routes. Optional.
basename: '/example'
})

const clientOnlyStore = createStore(
yourReducer,
initialState,
routerForBrowser({
// The configured routes. Required.
routes,
// The basename for all routes. Optional.
basename: '/example'
})
compose(
routerEnhancer,
applyMiddleware(routerMiddleware)
)
);
```

Expand Down
10 changes: 8 additions & 2 deletions demo/client/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,27 @@ import 'normalize.css/normalize.css';
import './global.css';

import { render } from 'react-dom';
import { createStore, compose } from 'redux';
import { createStore, compose, applyMiddleware } from 'redux';

import routerForBrowser from '../../src/browser-router';

import routes from './routes';
import wrap from './wrap';
import Demo from './demo';

const {
routerEnhancer,
routerMiddleware
} = routerForBrowser({ routes });

const store = createStore(
state => state,
// If this is a server render, we grab the
// initial state the hbs template inserted
window.__INITIAL_STATE || {},
compose(
routerForBrowser({ routes }),
routerEnhancer,
applyMiddleware(routerMiddleware),
window.devToolsExtension ?
window.devToolsExtension() : f => f
)
Expand Down
18 changes: 13 additions & 5 deletions demo/server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,18 @@ const encode = require('ent/encode');
const renderToString = require('react-dom/server')
.renderToString;

const createStore = require('redux').createStore;
const redux = require('redux');
const routerForExpress = require('../../src')
.routerForExpress;

const routes = require('../client/routes').default;
const wrap = require('../client/wrap').default;
const Root = require('../client/demo').default;

const createStore = redux.createStore;
const compose = redux.compose;
const applyMiddleware = redux.applyMiddleware;

const PORT = 4567;

const templateFile = fs.readFileSync(path.join(__dirname, './index.hbs'));
Expand Down Expand Up @@ -60,13 +64,17 @@ app.use('/favicon.ico', (req, res) => res.end());

app.get('/*', (req, res) => {
const initialState = {};
const router = routerForExpress({
routes,
request: req
});
const store = createStore(
state => state,
initialState,
routerForExpress({
routes,
request: req
})
compose(
router.routerEnhancer,
applyMiddleware(router.routerMiddleware)
)
);

const content = renderToString(wrap(store)(Root));
Expand Down
6 changes: 5 additions & 1 deletion src/browser-router.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import useBasename from 'history/lib/useBasename';
import useQueries from 'history/lib/useQueries';

import installRouter from './store-enhancer';
import routerMiddleware from './middleware';

type BrowserRouterArgs = {
routes: Object,
Expand All @@ -27,5 +28,8 @@ export default ({
const location = history
.createLocation({ pathname, search });

return installRouter({ routes, history, location });
return {
routerEnhancer: installRouter({ routes, history, location }),
routerMiddleware: routerMiddleware({ history })
};
};
6 changes: 5 additions & 1 deletion src/express-router.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import useBasename from 'history/lib/useBasename';
import useQueries from 'history/lib/useQueries';

import installRouter from './store-enhancer';
import routerMiddleware from './middleware';

type ServerRouterArgs = {
routes: Object,
Expand All @@ -25,5 +26,8 @@ export default ({ routes, request }: ServerRouterArgs) => {
query: request.query
});

return installRouter({ routes, history, location });
return {
routerEnhancer: installRouter({ routes, history, location }),
routerMiddleware: routerMiddleware({ history })
};
};
2 changes: 2 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import routerForBrowser from './browser-router';
import routerForExpress from './express-router';
import createStoreWithRouter from './store-enhancer';
import routerMiddleware from './middleware';
import { locationDidChange, initializeCurrentLocation } from './action-creators';

import provideRouter, { RouterProvider } from './provider';
Expand All @@ -26,6 +27,7 @@ export {
// High-level Redux API
routerForBrowser,
routerForExpress,
routerMiddleware,
initializeCurrentLocation,

// React API
Expand Down
17 changes: 7 additions & 10 deletions src/wrap-dispatch.js → src/middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,24 @@ import {
GO_BACK, GO_FORWARD
} from './action-types';

export default (store, history) => action => {
export default ({ history }) => () => next => action => {
switch (action.type) {
case PUSH:
history.push(action.payload);
return null;
break;
case REPLACE:
history.replace(action.payload);
return null;
break;
case GO:
history.go(action.payload);
return null;
break;
case GO_BACK:
history.goBack();
return null;
break;
case GO_FORWARD:
history.goForward();
return null;
break;
default:
// We return the result of dispatch here
// to retain compatibility with enhancers
// that return a promise from dispatch.
return store.dispatch(action);
next(action);
}
};
4 changes: 0 additions & 4 deletions src/store-enhancer.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import type { History } from 'history';
import { default as matcherFactory } from './create-matcher';
import attachRouterToReducer from './reducer-enhancer';
import { locationDidChange } from './action-creators';
import wrapDispatch from './wrap-dispatch';

import validateRoutes from './util/validate-routes';
import flattenRoutes from './util/flatten-routes';
Expand Down Expand Up @@ -65,11 +64,8 @@ export default ({
}
});

const dispatch = wrapDispatch(store, history);

return {
...store,
dispatch,

// We attach routes here to allow <RouterProvider>
// to access unserializable properties of route results.
Expand Down
32 changes: 17 additions & 15 deletions test/browser-router.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,17 @@ chai.use(sinonChai);

describe('Browser router', () => {
it('creates a browser store enhancer using window.location', () => {
const { routerEnhancer } = routerForBrowser({
routes,
getLocation: () => ({
pathname: '/home',
search: '?get=schwifty'
})
});
const store = createStore(
state => state,
{},
routerForBrowser({
routes,
getLocation: () => ({
pathname: '/home',
search: '?get=schwifty'
})
})
routerEnhancer
);
const state = store.getState();
expect(state).to.have.deep.property('router.pathname', '/home');
Expand All @@ -30,17 +31,18 @@ describe('Browser router', () => {
});

it('supports basenames', () => {
const { routerEnhancer } = routerForBrowser({
routes,
basename: '/cob-planet',
getLocation: () => ({
pathname: '/home',
search: '?get=schwifty'
})
});
const store = createStore(
state => state,
{},
routerForBrowser({
routes,
basename: '/cob-planet',
getLocation: () => ({
pathname: '/home',
search: '?get=schwifty'
})
})
routerEnhancer
);
const state = store.getState();
expect(state).to.have.deep.property('router.basename', '/cob-planet');
Expand Down
Loading