diff --git a/docs/advanced/Middleware.md b/docs/advanced/Middleware.md index fe51ff6493..f77aa88273 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` executes and sets up your middleware, the `store.dispatch` function will point to the vanilla version provided by `createStore`. Dispatching would result in no other middleware being applied. If you are expecting an interaction with another middleware during setup, you will probably be disappointed. Because of this unexpected behavior, `applyMiddleware` will throw an error if you try to dispatch an action before the set up completes. 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 3cf08841b6..a35098914f 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, preloadedState, enhancer) => { const store = createStore(reducer, preloadedState, enhancer) - let dispatch = store.dispatch + let dispatch = () => { + throw new Error( + `Dispatching while constructing your middleware is not allowed. ` + + `Other middleware would not be applied to this dispatch.` + ) + } let chain = [] const middlewareAPI = { diff --git a/test/applyMiddleware.spec.js b/test/applyMiddleware.spec.js index c733c093b8..84e7c79df6 100644 --- a/test/applyMiddleware.spec.js +++ b/test/applyMiddleware.spec.js @@ -4,6 +4,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 => {