diff --git a/src/applyMiddleware.js b/src/applyMiddleware.js index 52c95fc8a7..4085bef70c 100644 --- a/src/applyMiddleware.js +++ b/src/applyMiddleware.js @@ -7,23 +7,21 @@ import compose from './compose' * * See `redux-thunk` package as an example of the Redux middleware. * - * Because middleware is potentially asynchronous, this should be the first - * store enhancer in the composition chain. - * - * Note that each middleware will be given the `dispatch` and `getState` functions + * Note that each middleware will be given the `dispatch`, `schedule` and `getState` functions * as named arguments. * * @param {...Function} middlewares The middleware chain to be applied. * @returns {Function} A store enhancer applying the middleware. */ export default function applyMiddleware(...middlewares) { - return (createStore) => (reducer, initialState, enhancer) => { - var store = createStore(reducer, initialState, enhancer) + return (createStore) => (reducer, initialState, schedule) => { + var store = createStore(reducer, initialState, schedule) var dispatch = store.dispatch var chain = [] var middlewareAPI = { getState: store.getState, + schedule: schedule, dispatch: (action) => dispatch(action) } chain = middlewares.map(middleware => middleware(middlewareAPI)) diff --git a/src/createStore.js b/src/createStore.js index 4ab0356c38..d38ee38445 100644 --- a/src/createStore.js +++ b/src/createStore.js @@ -18,28 +18,10 @@ import ActionTypes from './utils/actionTypes' * If you use `combineReducers` to produce the root reducer function, this must be * an object with the same shape as `combineReducers` keys. * - * @param {Function} enhancer The store enhancer. You may optionally specify it - * to enhance the store with third-party capabilities such as middleware, - * time travel, persistence, etc. The only store enhancer that ships with Redux - * is `applyMiddleware()`. - * * @returns {Store} A Redux store that lets you read the state, dispatch actions * and subscribe to changes. */ -export default function createStore(reducer, initialState, enhancer) { - if (typeof initialState === 'function' && typeof enhancer === 'undefined') { - enhancer = initialState - initialState = undefined - } - - if (typeof enhancer !== 'undefined') { - if (typeof enhancer !== 'function') { - throw new Error('Expected the enhancer to be a function.') - } - - return enhancer(createStore)(reducer, initialState) - } - +export default function createStore(reducer, initialState) { if (typeof reducer !== 'function') { throw new Error('Expected the reducer to be a function.') } diff --git a/src/enhanceCreateStore.js b/src/enhanceCreateStore.js new file mode 100644 index 0000000000..fefe5a3fd6 --- /dev/null +++ b/src/enhanceCreateStore.js @@ -0,0 +1,34 @@ +import createStore from './createStore' + +/** + * Enhances the store creator in Redux, to allow scheduling of async `dispatch()`. + * + * @param {Function} enhancer The store enhancer. You may optionally specify it + * to enhance the store with third-party capabilities such as middleware, + * time travel, persistence, etc. The only store enhancer that ships with Redux + * is `applyMiddleware()`. + * + * @returns {Store} An enhanced Redux store that lets you read the state, dispatch actions + * and subscribe to changes. + */ +export default function enhanceCreateStore(enhancer) { + if (typeof enhancer !== 'undefined' && typeof enhancer !== 'function') { + throw new Error('Expected the enhancer to be a function.') + } + + return function (reducer, initialState) { + var finalStore = enhancer(createStore)(reducer, initialState, schedule) + + /** + * Queues an action for dispatch. This is useful when dispatching async + * actions as it will dispatch in the top store. This way all enhancers + * are to able to properly dispatch regardless if they are on top or not. + */ + function schedule(action) { + setTimeout(() => finalStore.dispatch(action)) + return action + } + + return finalStore + } +} diff --git a/src/index.js b/src/index.js index 5d0d8cd624..3328acf06f 100644 --- a/src/index.js +++ b/src/index.js @@ -1,4 +1,5 @@ import createStore from './createStore' +import enhanceCreateStore from './enhanceCreateStore' import combineReducers from './combineReducers' import bindActionCreators from './bindActionCreators' import applyMiddleware from './applyMiddleware' @@ -27,6 +28,7 @@ if ( export { createStore, + enhanceCreateStore, combineReducers, bindActionCreators, applyMiddleware, diff --git a/test/applyMiddleware.spec.js b/test/applyMiddleware.spec.js index 988981a1ce..f029caadad 100644 --- a/test/applyMiddleware.spec.js +++ b/test/applyMiddleware.spec.js @@ -23,6 +23,7 @@ describe('applyMiddleware', () => { expect(Object.keys(spy.calls[0].arguments[0])).toEqual([ 'getState', + 'schedule', 'dispatch' ]) diff --git a/test/createStore.spec.js b/test/createStore.spec.js index 3a8af0630b..17fc99613b 100644 --- a/test/createStore.spec.js +++ b/test/createStore.spec.js @@ -492,93 +492,6 @@ describe('createStore', () => { ).toNotThrow() }) - it('accepts enhancer as the third argument', () => { - const emptyArray = [] - const spyEnhancer = vanillaCreateStore => (...args) => { - expect(args[0]).toBe(reducers.todos) - expect(args[1]).toBe(emptyArray) - expect(args.length).toBe(2) - const vanillaStore = vanillaCreateStore(...args) - return { - ...vanillaStore, - dispatch: expect.createSpy(vanillaStore.dispatch).andCallThrough() - } - } - - const store = createStore(reducers.todos, emptyArray, spyEnhancer) - const action = addTodo('Hello') - store.dispatch(action) - expect(store.dispatch).toHaveBeenCalledWith(action) - expect(store.getState()).toEqual([ - { - id: 1, - text: 'Hello' - } - ]) - }) - - it('accepts enhancer as the second argument if initial state is missing', () => { - const spyEnhancer = vanillaCreateStore => (...args) => { - expect(args[0]).toBe(reducers.todos) - expect(args[1]).toBe(undefined) - expect(args.length).toBe(2) - const vanillaStore = vanillaCreateStore(...args) - return { - ...vanillaStore, - dispatch: expect.createSpy(vanillaStore.dispatch).andCallThrough() - } - } - - const store = createStore(reducers.todos, spyEnhancer) - const action = addTodo('Hello') - store.dispatch(action) - expect(store.dispatch).toHaveBeenCalledWith(action) - expect(store.getState()).toEqual([ - { - id: 1, - text: 'Hello' - } - ]) - }) - - it('throws if enhancer is neither undefined nor a function', () => { - expect(() => - createStore(reducers.todos, undefined, {}) - ).toThrow() - - expect(() => - createStore(reducers.todos, undefined, []) - ).toThrow() - - expect(() => - createStore(reducers.todos, undefined, null) - ).toThrow() - - expect(() => - createStore(reducers.todos, undefined, false) - ).toThrow() - - expect(() => - createStore(reducers.todos, undefined, undefined) - ).toNotThrow() - - expect(() => - createStore(reducers.todos, undefined, x => x) - ).toNotThrow() - - expect(() => - createStore(reducers.todos, x => x) - ).toNotThrow() - - expect(() => - createStore(reducers.todos, []) - ).toNotThrow() - - expect(() => - createStore(reducers.todos, {}) - ).toNotThrow() - }) - it('throws if nextReducer is not a function', () => { const store = createStore(reducers.todos)