Skip to content

Commit 0814ef3

Browse files
committed
Merge pull request #1294 from rackt/enhancers
Add first-class support for store enhancers to createStore() API
2 parents e2baf0c + 5a1b3e0 commit 0814ef3

File tree

13 files changed

+203
-101
lines changed

13 files changed

+203
-101
lines changed

docs/advanced/AsyncActions.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ Here’s what the state shape for our “Reddit headlines” app might look like
138138
{
139139
id: 42,
140140
title: 'Confusion about Flux and Relay'
141-
},
141+
},
142142
{
143143
id: 500,
144144
title: 'Creating a Simple Application Using React JS and Flux Architecture'
@@ -384,7 +384,7 @@ export function fetchPosts(subreddit) {
384384
>import 'babel-core/polyfill'
385385
>```
386386
387-
How do we include the Redux Thunk middleware in the dispatch mechanism? We use the [`applyMiddleware()`](../api/applyMiddleware.md) method from Redux, as shown below:
387+
How do we include the Redux Thunk middleware in the dispatch mechanism? We use the [`applyMiddleware()`](../api/applyMiddleware.md) store enhancer from Redux, as shown below:
388388
389389
#### `index.js`
390390
@@ -397,12 +397,13 @@ import rootReducer from './reducers'
397397
398398
const loggerMiddleware = createLogger()
399399
400-
const createStoreWithMiddleware = applyMiddleware(
401-
thunkMiddleware, // lets us dispatch() functions
402-
loggerMiddleware // neat middleware that logs actions
403-
)(createStore)
404-
405-
const store = createStoreWithMiddleware(rootReducer)
400+
const store = createStore(
401+
rootReducer,
402+
applyMiddleware(
403+
thunkMiddleware, // lets us dispatch() functions
404+
loggerMiddleware // neat middleware that logs actions
405+
)
406+
)
406407
407408
store.dispatch(selectSubreddit('reactjs'))
408409
store.dispatch(fetchPosts('reactjs')).then(() =>

docs/advanced/ExampleRedditAPI.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -170,13 +170,15 @@ import rootReducer from './reducers'
170170

171171
const loggerMiddleware = createLogger()
172172

173-
const createStoreWithMiddleware = applyMiddleware(
174-
thunkMiddleware,
175-
loggerMiddleware
176-
)(createStore)
177-
178173
export default function configureStore(initialState) {
179-
return createStoreWithMiddleware(rootReducer, initialState)
174+
return createStore(
175+
rootReducer,
176+
initialState,
177+
applyMiddleware(
178+
thunkMiddleware,
179+
loggerMiddleware
180+
)
181+
)
180182
}
181183
```
182184

docs/advanced/Middleware.md

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,8 @@ The implementation of [`applyMiddleware()`](../api/applyMiddleware.md) that ship
272272

273273
* 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`.
274274

275+
Because it is cumbersome to apply functions to `createStore()` before using it, `createStore()` accepts an optional last argument to specify such functions.
276+
275277
### The Final Approach
276278

277279
Given this middleware we just wrote:
@@ -305,13 +307,12 @@ Here’s how to apply it to a Redux store:
305307
```js
306308
import { createStore, combineReducers, applyMiddleware } from 'redux'
307309

308-
// applyMiddleware takes createStore() and returns
309-
// a function with a compatible API.
310-
let createStoreWithMiddleware = applyMiddleware(logger, crashReporter)(createStore)
311-
312-
// Use it like you would use createStore()
313310
let todoApp = combineReducers(reducers)
314-
let store = createStoreWithMiddleware(todoApp)
311+
let store = createStore(
312+
todoApp,
313+
// applyMiddleware() tells createStore() how to handle middleware
314+
applyMiddleware(logger, crashReporter)
315+
)
315316
```
316317

317318
That’s it! Now any actions dispatched to the store instance will flow through `logger` and `crashReporter`:
@@ -378,8 +379,8 @@ const timeoutScheduler = store => next => action => {
378379
}
379380

380381
/**
381-
* Schedules actions with { meta: { raf: true } } to be dispatched inside a rAF loop
382-
* frame. Makes `dispatch` return a function to remove the action from the queue in
382+
* Schedules actions with { meta: { raf: true } } to be dispatched inside a rAF loop
383+
* frame. Makes `dispatch` return a function to remove the action from the queue in
383384
* this case.
384385
*/
385386
const rafScheduler = store => next => {
@@ -472,15 +473,17 @@ const thunk = store => next => action =>
472473

473474

474475
// You can use all of them! (It doesn’t mean you should.)
475-
let createStoreWithMiddleware = applyMiddleware(
476-
rafScheduler,
477-
timeoutScheduler,
478-
thunk,
479-
vanillaPromise,
480-
readyStatePromise,
481-
logger,
482-
crashReporter
483-
)(createStore)
484476
let todoApp = combineReducers(reducers)
485-
let store = createStoreWithMiddleware(todoApp)
477+
let store = createStore(
478+
todoApp,
479+
applyMiddleware(
480+
rafScheduler,
481+
timeoutScheduler,
482+
thunk,
483+
vanillaPromise,
484+
readyStatePromise,
485+
logger,
486+
crashReporter
487+
)
488+
)
486489
```

docs/api/applyMiddleware.md

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ Middleware is not baked into [`createStore`](createStore.md) and is not a fundam
1414

1515
#### Returns
1616

17-
(*Function*) A store enhancer that applies the given middleware. The store enhancer is a function that needs to be applied to `createStore`. It will return a different `createStore` which has the middleware enabled.
17+
(*Function*) A store enhancer that applies the given middleware. The store enhancer signature is `createStore => createStore'` but the easiest way to apply it is to pass it to [`createStore()`](./createStore.md) as the last `enhancer` argument.
1818

1919
#### Example: Custom Logger Middleware
2020

@@ -37,8 +37,11 @@ function logger({ getState }) {
3737
}
3838
}
3939

40-
let createStoreWithMiddleware = applyMiddleware(logger)(createStore)
41-
let store = createStoreWithMiddleware(todos, [ 'Use Redux' ])
40+
let store = createStore(
41+
todos,
42+
[ 'Use Redux' ],
43+
applyMiddleware(logger)
44+
)
4245

4346
store.dispatch({
4447
type: 'ADD_TODO',
@@ -56,12 +59,9 @@ import { createStore, combineReducers, applyMiddleware } from 'redux'
5659
import thunk from 'redux-thunk'
5760
import * as reducers from './reducers'
5861

59-
// applyMiddleware supercharges createStore with middleware:
60-
let createStoreWithMiddleware = applyMiddleware(thunk)(createStore)
61-
62-
// We can use it exactly like “vanilla” createStore.
6362
let reducer = combineReducers(reducers)
64-
let store = createStoreWithMiddleware(reducer)
63+
// applyMiddleware supercharges createStore with middleware:
64+
let store = createStore(reducer, applyMiddleware(thunk))
6565

6666
function fetchSecretSauce() {
6767
return fetch('https://www.google.com/search?q=secret+sauce')
@@ -229,11 +229,18 @@ export default connect(
229229
let d = require('another-debug-middleware');
230230
middleware = [ ...middleware, c, d ];
231231
}
232-
const createStoreWithMiddleware = applyMiddleware(...middleware)(createStore);
232+
233+
const store = createStore(
234+
reducer,
235+
initialState,
236+
applyMiddleware(middleware)
237+
)
233238
```
234239

235240
This makes it easier for bundling tools to cut out unneeded modules and reduces the size of your builds.
236241

237242
* Ever wondered what `applyMiddleware` itself is? It ought to be an extension mechanism more powerful than the middleware itself. Indeed, `applyMiddleware` is an example of the most powerful Redux extension mechanism called [store enhancers](../Glossary.md#store-enhancer). It is highly unlikely you’ll ever want to write a store enhancer yourself. Another example of a store enhancer is [redux-devtools](https://github.com/gaearon/redux-devtools). Middleware is less powerful than a store enhancer, but it is easier to write.
238243

239244
* Middleware sounds much more complicated than it really is. The only way to really understand middleware is to see how the existing middleware works, and try to write your own. The function nesting can be intimidating, but most of the middleware you’ll find are, in fact, 10-liners, and the nesting and composability is what makes the middleware system powerful.
245+
246+
* To apply multiple store enhancers, you may use [`compose()`](./compose.md).

docs/api/compose.md

Lines changed: 10 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -20,40 +20,16 @@ This example demonstrates how to use `compose` to enhance a [store](Store.md) wi
2020
```js
2121
import { createStore, combineReducers, applyMiddleware, compose } from 'redux'
2222
import thunk from 'redux-thunk'
23-
import * as reducers from '../reducers/index'
24-
25-
let reducer = combineReducers(reducers)
26-
let middleware = [ thunk ]
27-
28-
let finalCreateStore
29-
30-
// In production, we want to use just the middleware.
31-
// In development, we want to use some store enhancers from redux-devtools.
32-
// UglifyJS will eliminate the dead code depending on the build environment.
33-
34-
if (process.env.NODE_ENV === 'production') {
35-
finalCreateStore = applyMiddleware(...middleware)(createStore)
36-
} else {
37-
finalCreateStore = compose(
38-
applyMiddleware(...middleware),
39-
require('redux-devtools').devTools(),
40-
require('redux-devtools').persistState(
41-
window.location.href.match(/[?&]debug_session=([^&]+)\b/)
42-
)
43-
)(createStore)
44-
45-
// Same code without the `compose` helper:
46-
//
47-
// finalCreateStore = applyMiddleware(middleware)(
48-
// require('redux-devtools').devTools()(
49-
// require('redux-devtools').persistState(
50-
// window.location.href.match(/[?&]debug_session=([^&]+)\b/)
51-
// )(createStore)
52-
// )
53-
// )
54-
}
55-
56-
let store = finalCreateStore(reducer)
23+
import DevTools from './containers/DevTools'
24+
import reducer from '../reducers/index'
25+
26+
const store = createStore(
27+
reducer,
28+
compose(
29+
applyMiddleware(thunk),
30+
DevTools.instrument()
31+
)
32+
)
5733
```
5834

5935
#### Tips

docs/api/createStore.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ There should only be a single store in your app.
99

1010
2. [`initialState`] *(any)*: The initial state. You may optionally specify it to hydrate the state from the server in universal apps, or to restore a previously serialized user session. If you produced `reducer` with [`combineReducers`](combineReducers.md), this must be a plain object with the same shape as the keys passed to it. Otherwise, you are free to pass anything that your `reducer` can understand.
1111

12+
3. [`enhancer`] *(Function)*: 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()`](./applyMiddleware.md).
13+
1214
#### Returns
1315

1416
([*`Store`*](Store.md)): An object that holds the complete state of your app. The only way to change its state is by [dispatching actions](Store.md#dispatch). You may also [subscribe](Store.md#subscribe) to the changes to its state to update the UI.
@@ -49,3 +51,5 @@ console.log(store.getState())
4951
* For universal apps that run on the server, create a store instance with every request so that they are isolated. Dispatch a few data fetching actions to a store instance and wait for them to complete before rendering the app on the server.
5052

5153
* When a store is created, Redux dispatches a dummy action to your reducer to populate the store with the initial state. You are not meant to handle the dummy action directly. Just remember that your reducer should return some kind of initial state if the state given to it as the first argument is `undefined`, and you’re all set.
54+
55+
* To apply multiple store enhancers, you may use [`compose()`](./compose.md).

examples/async/store/configureStore.js

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,12 @@ import thunkMiddleware from 'redux-thunk'
33
import createLogger from 'redux-logger'
44
import rootReducer from '../reducers'
55

6-
const createStoreWithMiddleware = applyMiddleware(
7-
thunkMiddleware,
8-
createLogger()
9-
)(createStore)
10-
116
export default function configureStore(initialState) {
12-
const store = createStoreWithMiddleware(rootReducer, initialState)
7+
const store = createStore(
8+
rootReducer,
9+
initialState,
10+
applyMiddleware(thunkMiddleware, createLogger())
11+
)
1312

1413
if (module.hot) {
1514
// Enable Webpack hot module replacement for reducers

examples/real-world/store/configureStore.dev.js

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,16 @@ import createLogger from 'redux-logger'
88
import rootReducer from '../reducers'
99

1010
const reduxRouterMiddleware = syncHistory(browserHistory)
11-
const finalCreateStore = compose(
12-
applyMiddleware(thunk, api, reduxRouterMiddleware, createLogger()),
13-
DevTools.instrument()
14-
)(createStore)
1511

1612
export default function configureStore(initialState) {
17-
const store = finalCreateStore(rootReducer, initialState)
13+
const store = createStore(
14+
rootReducer,
15+
initialState,
16+
compose(
17+
applyMiddleware(thunk, api, reduxRouterMiddleware, createLogger()),
18+
DevTools.instrument()
19+
)
20+
)
1821

1922
// Required for replaying actions from devtools to work
2023
reduxRouterMiddleware.listenForReplays(store)
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
import { createStore, applyMiddleware, compose } from 'redux'
1+
import { createStore, applyMiddleware } from 'redux'
22
import { syncHistory } from 'react-router-redux'
33
import { browserHistory } from 'react-router'
44
import thunk from 'redux-thunk'
55
import api from '../middleware/api'
66
import rootReducer from '../reducers'
77

8-
const finalCreateStore = compose(
9-
applyMiddleware(thunk, api, syncHistory(browserHistory)),
10-
)(createStore)
11-
128
export default function configureStore(initialState) {
13-
return finalCreateStore(rootReducer, initialState)
9+
return createStore(
10+
rootReducer,
11+
initialState,
12+
applyMiddleware(thunk, api, syncHistory(browserHistory))
13+
)
1414
}

examples/shopping-cart/index.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ const middleware = process.env.NODE_ENV === 'production' ?
1212
[ thunk ] :
1313
[ thunk, logger() ]
1414

15-
const createStoreWithMiddleware = applyMiddleware(...middleware)(createStore)
16-
const store = createStoreWithMiddleware(reducer)
15+
const store = createStore(
16+
reducer,
17+
applyMiddleware(...middleware)
18+
)
1719

1820
store.dispatch(getAllProducts())
1921

examples/universal/common/store/configureStore.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ import { createStore, applyMiddleware } from 'redux'
22
import thunk from 'redux-thunk'
33
import rootReducer from '../reducers'
44

5-
const createStoreWithMiddleware = applyMiddleware(
6-
thunk
7-
)(createStore)
8-
95
export default function configureStore(initialState) {
10-
const store = createStoreWithMiddleware(rootReducer, initialState)
6+
const store = createStore(
7+
rootReducer,
8+
initialState,
9+
applyMiddleware(thunk)
10+
)
1111

1212
if (module.hot) {
1313
// Enable Webpack hot module replacement for reducers

src/createStore.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,28 @@ export var ActionTypes = {
2727
* If you use `combineReducers` to produce the root reducer function, this must be
2828
* an object with the same shape as `combineReducers` keys.
2929
*
30+
* @param {Function} enhancer The store enhancer. You may optionally specify it
31+
* to enhance the store with third-party capabilities such as middleware,
32+
* time travel, persistence, etc. The only store enhancer that ships with Redux
33+
* is `applyMiddleware()`.
34+
*
3035
* @returns {Store} A Redux store that lets you read the state, dispatch actions
3136
* and subscribe to changes.
3237
*/
33-
export default function createStore(reducer, initialState) {
38+
export default function createStore(reducer, initialState, enhancer) {
39+
if (typeof initialState === 'function' && typeof enhancer === 'undefined') {
40+
enhancer = initialState
41+
initialState = undefined
42+
}
43+
44+
if (typeof enhancer !== 'undefined') {
45+
if (typeof enhancer !== 'function') {
46+
throw new Error('Expected the enhancer to be a function.')
47+
}
48+
49+
return enhancer(createStore)(reducer, initialState)
50+
}
51+
3452
if (typeof reducer !== 'function') {
3553
throw new Error('Expected the reducer to be a function.')
3654
}

0 commit comments

Comments
 (0)