Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add first-class support for store enhancers to createStore() API #1294

Merged
merged 5 commits into from
Jan 28, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions docs/advanced/AsyncActions.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ Here’s what the state shape for our “Reddit headlines” app might look like
{
id: 42,
title: 'Confusion about Flux and Relay'
},
},
{
id: 500,
title: 'Creating a Simple Application Using React JS and Flux Architecture'
Expand Down Expand Up @@ -384,7 +384,7 @@ export function fetchPosts(subreddit) {
>import 'babel-core/polyfill'
>```

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:
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:

#### `index.js`

Expand All @@ -397,12 +397,13 @@ import rootReducer from './reducers'

const loggerMiddleware = createLogger()

const createStoreWithMiddleware = applyMiddleware(
thunkMiddleware, // lets us dispatch() functions
loggerMiddleware // neat middleware that logs actions
)(createStore)

const store = createStoreWithMiddleware(rootReducer)
const store = createStore(
rootReducer,
applyMiddleware(
thunkMiddleware, // lets us dispatch() functions
loggerMiddleware // neat middleware that logs actions
)
)

store.dispatch(selectSubreddit('reactjs'))
store.dispatch(fetchPosts('reactjs')).then(() =>
Expand Down
14 changes: 8 additions & 6 deletions docs/advanced/ExampleRedditAPI.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,13 +170,15 @@ import rootReducer from './reducers'

const loggerMiddleware = createLogger()

const createStoreWithMiddleware = applyMiddleware(
thunkMiddleware,
loggerMiddleware
)(createStore)

export default function configureStore(initialState) {
return createStoreWithMiddleware(rootReducer, initialState)
return createStore(
rootReducer,
initialState,
applyMiddleware(
thunkMiddleware,
loggerMiddleware
)
)
}
```

Expand Down
39 changes: 21 additions & 18 deletions docs/advanced/Middleware.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,8 @@ The implementation of [`applyMiddleware()`](../api/applyMiddleware.md) that ship

* 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.

### The Final Approach

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

// applyMiddleware takes createStore() and returns
// a function with a compatible API.
let createStoreWithMiddleware = applyMiddleware(logger, crashReporter)(createStore)

// Use it like you would use createStore()
let todoApp = combineReducers(reducers)
let store = createStoreWithMiddleware(todoApp)
let store = createStore(
todoApp,
// applyMiddleware() tells createStore() how to handle middleware
applyMiddleware(logger, crashReporter)
)
```

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

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


// You can use all of them! (It doesn’t mean you should.)
let createStoreWithMiddleware = applyMiddleware(
rafScheduler,
timeoutScheduler,
thunk,
vanillaPromise,
readyStatePromise,
logger,
crashReporter
)(createStore)
let todoApp = combineReducers(reducers)
let store = createStoreWithMiddleware(todoApp)
let store = createStore(
todoApp,
applyMiddleware(
rafScheduler,
timeoutScheduler,
thunk,
vanillaPromise,
readyStatePromise,
logger,
crashReporter
)
)
```
25 changes: 16 additions & 9 deletions docs/api/applyMiddleware.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Middleware is not baked into [`createStore`](createStore.md) and is not a fundam

#### Returns

(*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.
(*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.

#### Example: Custom Logger Middleware

Expand All @@ -37,8 +37,11 @@ function logger({ getState }) {
}
}

let createStoreWithMiddleware = applyMiddleware(logger)(createStore)
let store = createStoreWithMiddleware(todos, [ 'Use Redux' ])
let store = createStore(
todos,
[ 'Use Redux' ],
applyMiddleware(logger)
)

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

// applyMiddleware supercharges createStore with middleware:
let createStoreWithMiddleware = applyMiddleware(thunk)(createStore)

// We can use it exactly like “vanilla” createStore.
let reducer = combineReducers(reducers)
let store = createStoreWithMiddleware(reducer)
// applyMiddleware supercharges createStore with middleware:
let store = createStore(reducer, applyMiddleware(thunk))

function fetchSecretSauce() {
return fetch('https://www.google.com/search?q=secret+sauce')
Expand Down Expand Up @@ -229,11 +229,18 @@ export default connect(
let d = require('another-debug-middleware');
middleware = [ ...middleware, c, d ];
}
const createStoreWithMiddleware = applyMiddleware(...middleware)(createStore);

const store = createStore(
reducer,
initialState,
applyMiddleware(middleware)
)
```

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

* 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.

* 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.

* To apply multiple store enhancers, you may use [`compose()`](./compose.md).
44 changes: 10 additions & 34 deletions docs/api/compose.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,40 +20,16 @@ This example demonstrates how to use `compose` to enhance a [store](Store.md) wi
```js
import { createStore, combineReducers, applyMiddleware, compose } from 'redux'
import thunk from 'redux-thunk'
import * as reducers from '../reducers/index'

let reducer = combineReducers(reducers)
let middleware = [ thunk ]

let finalCreateStore

// In production, we want to use just the middleware.
// In development, we want to use some store enhancers from redux-devtools.
// UglifyJS will eliminate the dead code depending on the build environment.

if (process.env.NODE_ENV === 'production') {
finalCreateStore = applyMiddleware(...middleware)(createStore)
} else {
finalCreateStore = compose(
applyMiddleware(...middleware),
require('redux-devtools').devTools(),
require('redux-devtools').persistState(
window.location.href.match(/[?&]debug_session=([^&]+)\b/)
)
)(createStore)

// Same code without the `compose` helper:
//
// finalCreateStore = applyMiddleware(middleware)(
// require('redux-devtools').devTools()(
// require('redux-devtools').persistState(
// window.location.href.match(/[?&]debug_session=([^&]+)\b/)
// )(createStore)
// )
// )
}

let store = finalCreateStore(reducer)
import DevTools from './containers/DevTools'
import reducer from '../reducers/index'

const store = createStore(
reducer,
compose(
applyMiddleware(thunk),
DevTools.instrument()
)
)
```

#### Tips
Expand Down
4 changes: 4 additions & 0 deletions docs/api/createStore.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ There should only be a single store in your app.

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.

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).

#### Returns

([*`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.
Expand Down Expand Up @@ -49,3 +51,5 @@ console.log(store.getState())
* 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.

* 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.

* To apply multiple store enhancers, you may use [`compose()`](./compose.md).
11 changes: 5 additions & 6 deletions examples/async/store/configureStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@ import thunkMiddleware from 'redux-thunk'
import createLogger from 'redux-logger'
import rootReducer from '../reducers'

const createStoreWithMiddleware = applyMiddleware(
thunkMiddleware,
createLogger()
)(createStore)

export default function configureStore(initialState) {
const store = createStoreWithMiddleware(rootReducer, initialState)
const store = createStore(
rootReducer,
initialState,
applyMiddleware(thunkMiddleware, createLogger())
)

if (module.hot) {
// Enable Webpack hot module replacement for reducers
Expand Down
13 changes: 8 additions & 5 deletions examples/real-world/store/configureStore.dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@ import createLogger from 'redux-logger'
import rootReducer from '../reducers'

const reduxRouterMiddleware = syncHistory(browserHistory)
const finalCreateStore = compose(
applyMiddleware(thunk, api, reduxRouterMiddleware, createLogger()),
DevTools.instrument()
)(createStore)

export default function configureStore(initialState) {
const store = finalCreateStore(rootReducer, initialState)
const store = createStore(
rootReducer,
initialState,
compose(
applyMiddleware(thunk, api, reduxRouterMiddleware, createLogger()),
DevTools.instrument()
)
)

// Required for replaying actions from devtools to work
reduxRouterMiddleware.listenForReplays(store)
Expand Down
12 changes: 6 additions & 6 deletions examples/real-world/store/configureStore.prod.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { createStore, applyMiddleware, compose } from 'redux'
import { createStore, applyMiddleware } from 'redux'
import { syncHistory } from 'react-router-redux'
import { browserHistory } from 'react-router'
import thunk from 'redux-thunk'
import api from '../middleware/api'
import rootReducer from '../reducers'

const finalCreateStore = compose(
applyMiddleware(thunk, api, syncHistory(browserHistory)),
)(createStore)

export default function configureStore(initialState) {
return finalCreateStore(rootReducer, initialState)
return createStore(
rootReducer,
initialState,
applyMiddleware(thunk, api, syncHistory(browserHistory))
)
}
6 changes: 4 additions & 2 deletions examples/shopping-cart/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ const middleware = process.env.NODE_ENV === 'production' ?
[ thunk ] :
[ thunk, logger() ]

const createStoreWithMiddleware = applyMiddleware(...middleware)(createStore)
const store = createStoreWithMiddleware(reducer)
const store = createStore(
reducer,
applyMiddleware(...middleware)
)

store.dispatch(getAllProducts())

Expand Down
10 changes: 5 additions & 5 deletions examples/universal/common/store/configureStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import rootReducer from '../reducers'

const createStoreWithMiddleware = applyMiddleware(
thunk
)(createStore)

export default function configureStore(initialState) {
const store = createStoreWithMiddleware(rootReducer, initialState)
const store = createStore(
rootReducer,
initialState,
applyMiddleware(thunk)
)

if (module.hot) {
// Enable Webpack hot module replacement for reducers
Expand Down
20 changes: 19 additions & 1 deletion src/createStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,28 @@ export var 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) {
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)
}

if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.')
}
Expand Down
Loading