diff --git a/ADVANCED.md b/ADVANCED.md index 0d4abcb6..76eb2bdc 100644 --- a/ADVANCED.md +++ b/ADVANCED.md @@ -37,7 +37,7 @@ app.use('/*', (req, res) => { // routerForExpress will infer the basename // from req.baseUrl! // - const { reducer, middleware, enhancer } = routerForExpress({ + const { reducer, middleware, connect } = routerForExpress({ routes, request: req }) @@ -45,8 +45,10 @@ app.use('/*', (req, res) => { const store = createStore( combineReducers({ router: reducer }), { what: 'ever' }, - compose(enhancer, applyMiddleware(middleware)) + applyMiddleware(middleware) ); + + connect(store); // ...then renderToString() your components as usual, // passing your new store to your component. @@ -86,7 +88,7 @@ server.route({ // Create the Redux store, passing in the Hapi // request to the routerForHapi factory. - const { reducer, middleware, enhancer } = routerForHapi({ + const { reducer, middleware, connect } = routerForHapi({ routes, request }) @@ -94,8 +96,10 @@ server.route({ const store = createStore( reducer, { what: 'ever' }, - compose(enhancer, applyMiddleware(middleware)) + applyMiddleware(middleware) ); + + connect(store); // ...then renderToString() your components as usual, // passing your new store to your component. @@ -125,16 +129,18 @@ const routes = { const { reducer, - enhancer, + connect, middleware } = routerForBrowser({ routes }); const store = createStore( combineReducers({ router: reducer }), window.__INITIAL_STATE, - compose(enhancer, applyMiddleware(middleware)) + applyMiddleware(middleware) ); +connect(store); + // ...then render() your components as usual, // passing your new store to your component. ``` diff --git a/README.md b/README.md index d07992eb..9de81368 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,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. `redux-little-router` also adds a middleware that intercepts navigation actions and calls their equivalent method in `history`. +To connect into Redux applications, `redux-little-router` uses a store connector that wraps the `history` module and adds current and previous router state to your store. The connector 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 @@ -72,11 +72,11 @@ const routes = { // Install the router into the store for a browser-only environment. // routerForBrowser is a factory method that returns a store -// enhancer and a middleware. +// connect and a middleware. const { reducer, middleware, - enhancer + connect } = routerForBrowser({ // The configured routes. Required. routes, @@ -87,11 +87,13 @@ const { const clientOnlyStore = createStore( combineReducers({ router: reducer, yourReducer }), initialState, - compose(enhancer, applyMiddleware(middleware)) + applyMiddleware(middleware) ); + +connect(clientOnlyStore); ``` -Often, you'll want to update state or trigger side effects after loading the initial URL. To maintain compatibility with other store enhancers (particularly ones that handle side effects, like `redux-loop` or `redux-saga`), we require this optional initial dispatch to happen in client code by doing the following: +Often, you'll want to update state or trigger side effects after loading the initial URL. To maintain compatibility with store enhancers (particularly ones that handle side effects, like `redux-loop` or `redux-saga`), we require this optional initial dispatch to happen in client code by doing the following: ```js import { initializeCurrentLocation } from 'redux-little-router'; @@ -164,7 +166,7 @@ export const redirect = href => dispatch => { }; ``` -On location changes, the store enhancer dispatches a `LOCATION_CHANGED` action that contains at least the following properties: +On location changes, the router connector dispatches a `LOCATION_CHANGED` action that contains at least the following properties: ```js // For a URL matching /messages/:user diff --git a/demo/client/app.js b/demo/client/app.js index 4d564918..b716f9de 100644 --- a/demo/client/app.js +++ b/demo/client/app.js @@ -10,7 +10,7 @@ import routes from './routes'; import wrap from './wrap'; import Demo from './demo'; -const { reducer, enhancer, middleware } = routerForBrowser({ routes }); +const { reducer, connect, middleware } = routerForBrowser({ routes }); const store = createStore( combineReducers({ router: reducer }), @@ -18,12 +18,11 @@ const store = createStore( // initial state the hbs template inserted window.__INITIAL_STATE || {}, compose( - enhancer, applyMiddleware(middleware), window.devToolsExtension ? window.devToolsExtension() : f => f ) ); - +connect(store); const initialLocation = store.getState().router; if (initialLocation) { store.dispatch(initializeCurrentLocation(initialLocation)); diff --git a/demo/server/index.js b/demo/server/index.js index 498614cc..37f8ce98 100644 --- a/demo/server/index.js +++ b/demo/server/index.js @@ -33,7 +33,6 @@ const Root = require('../client/demo').default; const createStore = redux.createStore; const combineReducers = redux.combineReducers; -const compose = redux.compose; const applyMiddleware = redux.applyMiddleware; const PORT = 4567; @@ -83,9 +82,10 @@ app.get('/*', (req, res) => { const store = createStore( combineReducers({ router: router.reducer }), initialState, - compose(router.enhancer, applyMiddleware(router.middleware)) + applyMiddleware(router.middleware) ); + router.connect(store); const content = renderToString(wrap(store)(Root)); return res.send( diff --git a/src/enhancer.js b/src/connector.js similarity index 74% rename from src/enhancer.js rename to src/connector.js index 05da172f..df37a3e0 100644 --- a/src/enhancer.js +++ b/src/connector.js @@ -1,10 +1,8 @@ // @flow -import type { StoreCreator, Reducer, StoreEnhancer } from 'redux'; +import type { Store } from 'redux'; import type { History } from 'history'; -import type { Location } from './types'; - import qs from 'query-string'; import { POP } from './types'; @@ -12,26 +10,17 @@ import { locationDidChange, didReplaceRoutes, replace } from './actions'; import matchCache from './util/match-cache'; -type InitialState = { - router: Location -}; - -type EnhancerArgs = {| +type ConnectorArgs = {| history: History, matchRoute: Function, createMatcher: Function |}; -export default ({ history, matchRoute, createMatcher }: EnhancerArgs) => ( - createStore: StoreCreator<*, *> -) => ( - userReducer: Reducer<*, *>, - initialState: InitialState, - enhancer: StoreEnhancer<*, *> + +export default ({ history, matchRoute, createMatcher }: ConnectorArgs) => ( + store: Store<*, *> ) => { let currentMatcher = matchRoute; - const store = createStore(userReducer, initialState, enhancer); - // Replace the matcher when replacing routes store.subscribe(() => { const { @@ -75,9 +64,4 @@ export default ({ history, matchRoute, createMatcher }: EnhancerArgs) => ( }) ); }); - - return { - ...store, - matchRoute - }; }; diff --git a/src/install.js b/src/install.js index 9014daf0..50deae0c 100644 --- a/src/install.js +++ b/src/install.js @@ -4,7 +4,7 @@ import type { Location } from './types'; import reducer from './reducer'; import middleware from './middleware'; -import enhancer from './enhancer'; +import connector from './connector'; import { default as matcherFactory } from './util/create-matcher'; import validateRoutes from './util/validate-routes'; @@ -36,7 +36,7 @@ export default ({ } }), middleware: middleware({ history }), - enhancer: enhancer({ + connect: connector({ history, matchRoute, createMatcher diff --git a/test/enhancer.spec.js b/test/connector.spec.js similarity index 76% rename from test/enhancer.spec.js rename to test/connector.spec.js index 409a3946..29eb9a69 100644 --- a/test/enhancer.spec.js +++ b/test/connector.spec.js @@ -1,7 +1,7 @@ import chai, { expect } from 'chai'; import sinonChai from 'sinon-chai'; -import { combineReducers, compose, createStore, applyMiddleware } from 'redux'; +import { combineReducers, createStore, applyMiddleware } from 'redux'; import { PUSH, REPLACE_ROUTES } from '../src/types'; import install from '../src/install'; @@ -11,7 +11,7 @@ import defaultRoutes from './test-util/fixtures/routes'; chai.use(sinonChai); -describe('Router store enhancer', () => { +describe('Router store connector', () => { let store; let historyStub; let listenStub; @@ -24,7 +24,7 @@ describe('Router store enhancer', () => { const replace = sandbox.spy(() => listen(listenStub)); historyStub = { push, replace, listen }; - const { reducer, middleware, enhancer } = install({ + const { reducer, middleware, connect } = install({ routes: defaultRoutes, history: historyStub, location: { pathname: '/' } @@ -33,8 +33,9 @@ describe('Router store enhancer', () => { store = createStore( combineReducers({ router: reducer }), {}, - compose(enhancer, applyMiddleware(middleware)) + applyMiddleware(middleware) ); + connect(store); sandbox.spy(store, 'dispatch'); }); @@ -49,10 +50,6 @@ describe('Router store enhancer', () => { expect(store.dispatch).to.be.calledOnce; }); - it('attaches the matcher to the store', () => { - expect(store).to.have.property('matchRoute'); - }); - it('replaces routes', () => { store.dispatch({ type: REPLACE_ROUTES, @@ -64,9 +61,7 @@ describe('Router store enhancer', () => { } }); - // This dispatch isn't the dispatch used in the enhancer - // (each enhancer has its own copy of dispatch) - expect(store.dispatch).to.be.calledOnce; + expect(store.dispatch).to.be.calledThrice; expect(historyStub.replace).to.be.calledOnce; expect(listenStub).to.be.calledOnce; diff --git a/test/environment/browser-router.spec.js b/test/environment/browser-router.spec.js index def8edc1..fcea108a 100644 --- a/test/environment/browser-router.spec.js +++ b/test/environment/browser-router.spec.js @@ -1,7 +1,7 @@ import chai, { expect } from 'chai'; import sinonChai from 'sinon-chai'; -import { applyMiddleware, combineReducers, createStore, compose } from 'redux'; +import { applyMiddleware, combineReducers, createStore } from 'redux'; import routerForBrowser from '../../src/environment/browser-router'; @@ -10,8 +10,8 @@ import routes from '../test-util/fixtures/routes'; chai.use(sinonChai); describe('Browser router', () => { - it('creates a browser store enhancer using history location', () => { - const { enhancer, middleware, reducer } = routerForBrowser({ + it('creates a browser store connector using history location', () => { + const { connect, middleware, reducer } = routerForBrowser({ routes, history: { location: { @@ -25,8 +25,9 @@ describe('Browser router', () => { const store = createStore( combineReducers({ router: reducer }), {}, - compose(enhancer, applyMiddleware(middleware)) + applyMiddleware(middleware) ); + connect(store); const state = store.getState(); expect(state).to.have.nested.property('router.pathname', '/home'); expect(state).to.have.nested.property('router.search', '?get=schwifty'); @@ -37,7 +38,7 @@ describe('Browser router', () => { }); it('supports basenames', () => { - const { enhancer, middleware, reducer } = routerForBrowser({ + const { connect, middleware, reducer } = routerForBrowser({ routes, basename: '/cob-planet', history: { @@ -52,8 +53,9 @@ describe('Browser router', () => { const store = createStore( combineReducers({ router: reducer }), {}, - compose(enhancer, applyMiddleware(middleware)) + applyMiddleware(middleware) ); + connect(store); const state = store.getState(); expect(state).to.have.nested.property('router.basename', '/cob-planet'); expect(state).to.have.nested.property('router.pathname', '/home'); diff --git a/test/environment/express-router.spec.js b/test/environment/express-router.spec.js index 0c3a97ea..80f08b96 100644 --- a/test/environment/express-router.spec.js +++ b/test/environment/express-router.spec.js @@ -1,7 +1,7 @@ import chai, { expect } from 'chai'; import sinonChai from 'sinon-chai'; -import { applyMiddleware, combineReducers, createStore, compose } from 'redux'; +import { applyMiddleware, combineReducers, createStore } from 'redux'; import routerForExpress from '../../src/environment/express-router'; @@ -10,8 +10,8 @@ import routes from '../test-util/fixtures/routes'; chai.use(sinonChai); describe('Express router', () => { - it('creates a server store enhancer using Express request object', () => { - const { enhancer, middleware, reducer } = routerForExpress({ + it('creates a server store connector using Express request object', () => { + const { connect, middleware, reducer } = routerForExpress({ routes, request: { path: '/home', @@ -21,8 +21,9 @@ describe('Express router', () => { const store = createStore( combineReducers({ router: reducer }), {}, - compose(enhancer, applyMiddleware(middleware)) + applyMiddleware(middleware) ); + connect(store); const state = store.getState(); expect(state).to.have.nested.property('router.pathname', '/home'); expect(state).to.have.nested.property('router.search', '?get=schwifty'); @@ -32,7 +33,7 @@ describe('Express router', () => { }); it('supports basenames', () => { - const { enhancer, middleware, reducer } = routerForExpress({ + const { connect, middleware, reducer } = routerForExpress({ routes, request: { baseUrl: '/cob-planet', @@ -43,8 +44,9 @@ describe('Express router', () => { const store = createStore( combineReducers({ router: reducer }), {}, - compose(enhancer, applyMiddleware(middleware)) + applyMiddleware(middleware) ); + connect(store); const state = store.getState(); expect(state).to.have.nested.property('router.basename', '/cob-planet'); expect(state).to.have.nested.property('router.pathname', '/home'); diff --git a/test/environment/hapi-router.spec.js b/test/environment/hapi-router.spec.js index a5a3f4d3..acf42cd7 100644 --- a/test/environment/hapi-router.spec.js +++ b/test/environment/hapi-router.spec.js @@ -1,7 +1,7 @@ import chai, { expect } from 'chai'; import sinonChai from 'sinon-chai'; -import { applyMiddleware, combineReducers, createStore, compose } from 'redux'; +import { applyMiddleware, combineReducers, createStore } from 'redux'; import routerForHapi from '../../src/environment/hapi-router'; @@ -10,8 +10,8 @@ import routes from '../test-util/fixtures/routes'; chai.use(sinonChai); describe('Hapi router', () => { - it('creates a server store enhancer using Hapi request object', () => { - const { enhancer, middleware, reducer } = routerForHapi({ + it('creates a server store connector using Hapi request object', () => { + const { connect, middleware, reducer } = routerForHapi({ routes, request: { path: '/home', @@ -21,8 +21,9 @@ describe('Hapi router', () => { const store = createStore( combineReducers({ router: reducer }), {}, - compose(enhancer, applyMiddleware(middleware)) + applyMiddleware(middleware) ); + connect(store); const state = store.getState(); expect(state).to.have.nested.property('router.pathname', '/home'); expect(state).to.have.nested.property('router.search', '?get=schwifty'); diff --git a/test/environment/hash-router.spec.js b/test/environment/hash-router.spec.js index 1ba96335..bf4d2c91 100644 --- a/test/environment/hash-router.spec.js +++ b/test/environment/hash-router.spec.js @@ -1,7 +1,7 @@ import chai, { expect } from 'chai'; import sinonChai from 'sinon-chai'; -import { applyMiddleware, combineReducers, createStore, compose } from 'redux'; +import { applyMiddleware, combineReducers, createStore } from 'redux'; import routerForHash from '../../src/environment/hash-router'; @@ -10,7 +10,7 @@ import routes from '../test-util/fixtures/routes'; chai.use(sinonChai); describe('Hash router', () => { - it('creates a browser store enhancer using window.location', () => { + it('creates a browser store connector using window.location', () => { const history = { listen() {}, location: { @@ -18,15 +18,16 @@ describe('Hash router', () => { search: '?get=schwifty' } }; - const { enhancer, middleware, reducer } = routerForHash({ + const { connect, middleware, reducer } = routerForHash({ routes, history }); const store = createStore( combineReducers({ router: reducer }), {}, - compose(enhancer, applyMiddleware(middleware)) + applyMiddleware(middleware) ); + connect(store); const state = store.getState(); expect(state).to.have.nested.property('router.pathname', '/home'); expect(state).to.have.nested.property('router.search', '?get=schwifty'); @@ -44,7 +45,7 @@ describe('Hash router', () => { } }; - const { enhancer, middleware, reducer } = routerForHash({ + const { connect, middleware, reducer } = routerForHash({ routes, history, basename: '/cob-planet' @@ -52,8 +53,9 @@ describe('Hash router', () => { const store = createStore( combineReducers({ router: reducer }), {}, - compose(enhancer, applyMiddleware(middleware)) + applyMiddleware(middleware) ); + connect(store); const state = store.getState(); expect(state).to.have.nested.property('router.basename', '/cob-planet'); expect(state).to.have.nested.property('router.pathname', '/home');