diff --git a/docs/advanced/Middleware.md b/docs/advanced/Middleware.md index badc968e4e..8731706db0 100644 --- a/docs/advanced/Middleware.md +++ b/docs/advanced/Middleware.md @@ -268,12 +268,16 @@ The implementation of [`applyMiddleware()`](../api/applyMiddleware.md) that ship * It only exposes a subset of the [store API](../api/Store.md) to the middleware: [`dispatch(action)`](../api/Store.md#dispatch) and [`getState()`](../api/Store.md#getState). -* It does a bit of trickery to make sure that if you call `store.dispatch(action)` from your middleware instead of `next(action)`, the action will actually travel the whole middleware chain again, including the current middleware. This is useful for asynchronous middleware, as we have seen [previously](AsyncActions.md). +* It does a bit of trickery to make sure that if you call `store.dispatch(action)` from your middleware instead of `next(action)`, the action will actually travel the whole middleware chain again, including the current middleware. This is useful for asynchronous middleware, as we have seen [previously](AsyncActions.md). There is one caveat when calling `dispatch` during setup, described below. * To ensure that you may only apply middleware once, it operates on `createStore()` rather than on `store` itself. Instead of `(store, middlewares) => store`, its signature is `(...middlewares) => (createStore) => createStore`. Because it is cumbersome to apply functions to `createStore()` before using it, `createStore()` accepts an optional last argument to specify such functions. +#### Caveat: Dispatching During Setup + +While `applyMiddleware` sets up your middleware, the `store.dispatch` function will point to the vanilla version provided by `createStore`. None of the other middleware will be applied to this `dispatch` until after setup is complete. If you are expecting an interaction with another middleware during setup, you will probably be disappointed. Instead, you should either communicate directly with that other middleware via a common object (for an API calling middleware, this may be your API client object) or waiting until after the middleware is constructed with a callback. + ### The Final Approach Given this middleware we just wrote: diff --git a/src/applyMiddleware.js b/src/applyMiddleware.js index 52c95fc8a7..7a7dcb9b6e 100644 --- a/src/applyMiddleware.js +++ b/src/applyMiddleware.js @@ -19,7 +19,12 @@ import compose from './compose' export default function applyMiddleware(...middlewares) { return (createStore) => (reducer, initialState, enhancer) => { var store = createStore(reducer, initialState, enhancer) - var dispatch = store.dispatch + var dispatch = () => { + throw new Error( + `Dispatching while constructing your middleware is not allowed. ` + + `Other middleware would not be applied to this dispatch.` + ) + } var chain = [] var middlewareAPI = { diff --git a/test/applyMiddleware.spec.js b/test/applyMiddleware.spec.js index 988981a1ce..be1fe16a3f 100644 --- a/test/applyMiddleware.spec.js +++ b/test/applyMiddleware.spec.js @@ -5,6 +5,17 @@ import { addTodo, addTodoAsync, addTodoIfEmpty } from './helpers/actionCreators' import { thunk } from './helpers/middleware' describe('applyMiddleware', () => { + it('warns when dispatching during middleware setup', () => { + function dispatchingMiddleware(store) { + store.dispatch(addTodo('Dont dispatch in middleware setup')) + return next => action => next(action) + } + + expect(() => + applyMiddleware(dispatchingMiddleware)(createStore)(reducers.todos) + ).toThrow() + }) + it('wraps dispatch method with middleware once', () => { function test(spyOnMethods) { return methods => {