From ad49ffaa5366adc1e840f28c753f39ada7aac9a5 Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Thu, 18 Jun 2015 13:27:42 -0700 Subject: [PATCH 01/33] Add terminology.md --- docs/terminology.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/terminology.md diff --git a/docs/terminology.md b/docs/terminology.md new file mode 100644 index 0000000000..e69de29bb2 From 22f377b9eda3c461bb53a2fe1779d804806d0ba4 Mon Sep 17 00:00:00 2001 From: Nicola Molinari Date: Thu, 18 Jun 2015 23:54:09 +0200 Subject: [PATCH 02/33] Start FAQ document --- docs/faq.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 docs/faq.md diff --git a/docs/faq.md b/docs/faq.md new file mode 100644 index 0000000000..6f3de57488 --- /dev/null +++ b/docs/faq.md @@ -0,0 +1,3 @@ +**WIP** + +## RR integration From d96f5f4d9e8a26eb51508d533de00a524e496607 Mon Sep 17 00:00:00 2001 From: Nicola Molinari Date: Mon, 29 Jun 2015 22:37:55 +0200 Subject: [PATCH 03/33] Add some JSDoc --- src/middleware/thunk.js | 9 +++++++++ src/utils/bindActionCreators.js | 8 ++++++++ src/utils/getDisplayName.js | 6 ++++++ 3 files changed, 23 insertions(+) diff --git a/src/middleware/thunk.js b/src/middleware/thunk.js index a80a78159c..4500e5aeb0 100644 --- a/src/middleware/thunk.js +++ b/src/middleware/thunk.js @@ -1,3 +1,12 @@ +/** + * Given an action that returns a function, delegates its call + * to the function itself by passing the control of the dispatching, + * otherwise simply dispatch the action. + * In other words, it allows to have async actions. + * + * @param {Function} getState - allow to access the current state + * @return {Function} a new middleware + */ export default function thunkMiddleware(getState) { return (next) => { const recurse = (action) => diff --git a/src/utils/bindActionCreators.js b/src/utils/bindActionCreators.js index f9a81a5a80..e4a757b980 100644 --- a/src/utils/bindActionCreators.js +++ b/src/utils/bindActionCreators.js @@ -1,5 +1,13 @@ import mapValues from '../utils/mapValues'; +/** + * Given a list action creators, wrap them to the `dispatch` function + * in order to be automatically dispatched when invoked. + * + * @param {Object} actionCreators - an object with the action functions + * @param {Function} dispatch + * @return {Object} the given object with wrapped actions + */ export default function bindActionCreators(actionCreators, dispatch) { return mapValues(actionCreators, actionCreator => (...args) => dispatch(actionCreator(...args)) diff --git a/src/utils/getDisplayName.js b/src/utils/getDisplayName.js index 512702c87a..54f3263588 100644 --- a/src/utils/getDisplayName.js +++ b/src/utils/getDisplayName.js @@ -1,3 +1,9 @@ +/** + * Given a React component, return its name to be displayed. + * + * @param {React} Component + * @return {String} the name of the component + */ export default function getDisplayName(Component) { return Component.displayName || Component.name || 'Component'; } From af897523c00cdc657425f705879755d0a6380fe9 Mon Sep 17 00:00:00 2001 From: Nicola Molinari Date: Mon, 29 Jun 2015 23:18:40 +0200 Subject: [PATCH 04/33] More JSDoc --- src/utils/composeMiddleware.js | 6 ++++++ src/utils/composeStores.js | 10 ++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/utils/composeMiddleware.js b/src/utils/composeMiddleware.js index 596403919d..4f9f47f3e8 100644 --- a/src/utils/composeMiddleware.js +++ b/src/utils/composeMiddleware.js @@ -1,3 +1,9 @@ +/** + * Given a list of middlewares, compose them from left to right. + * + * @param {Array} middlewares - a list of middleware functions + * @return {Function} the combined middleware function + */ export default function composeMiddleware(...middlewares) { return middlewares.reduceRight((composed, m) => m(composed)); } diff --git a/src/utils/composeStores.js b/src/utils/composeStores.js index d8c4420546..ebed3f1baf 100644 --- a/src/utils/composeStores.js +++ b/src/utils/composeStores.js @@ -1,6 +1,16 @@ import mapValues from '../utils/mapValues'; import pick from '../utils/pick'; +/** + * Given a list of stores, maps the state keys to reducer functions. + * The composed store, when invoked, will internally map all the + * results of the computed stores (being effectively the state). + * When the store function is invoked again, it will pass the + * existing state value as initial state. + * + * @param {Object} stores - an object with the store reducer functions + * @return {Function} the composed store function + */ export default function composeStores(stores) { const finalStores = pick(stores, (val) => typeof val === 'function'); return function Composition(atom = {}, action) { From da2fa45775efed620a96e0b5ec15ae0ee56b4622 Mon Sep 17 00:00:00 2001 From: Nicola Molinari Date: Mon, 29 Jun 2015 23:38:09 +0200 Subject: [PATCH 05/33] More JSDoc --- src/utils/createReduxShape.js | 6 ++++++ src/utils/identity.js | 6 ++++++ src/utils/isPlainObject.js | 10 +++++++++- src/utils/mapValues.js | 9 +++++++++ src/utils/pick.js | 9 +++++++++ 5 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/utils/createReduxShape.js b/src/utils/createReduxShape.js index e3795bedd2..d79e1b7f98 100644 --- a/src/utils/createReduxShape.js +++ b/src/utils/createReduxShape.js @@ -1,3 +1,9 @@ +/** + * Define the shape of the `redux` prop, used for props validation. + * + * @param {Object} PropTypes + * @return {Object} + */ export default function createReduxShape(PropTypes) { return PropTypes.shape({ subscribe: PropTypes.func.isRequired, diff --git a/src/utils/identity.js b/src/utils/identity.js index 8c690a8859..f342078921 100644 --- a/src/utils/identity.js +++ b/src/utils/identity.js @@ -1,3 +1,9 @@ +/** + * Returns the first argument provided to it. + * + * @param {*} value + * @return {*} + */ export default function identity(value) { return value; } diff --git a/src/utils/isPlainObject.js b/src/utils/isPlainObject.js index a5845486cf..1a312c5d3f 100644 --- a/src/utils/isPlainObject.js +++ b/src/utils/isPlainObject.js @@ -1,3 +1,11 @@ +/** + * Given an object, checks that it's a plain object. That is, an object + * created by the `Object` constructor or one with a `Prototype` of `null`. + * + * @param {*} obj - the value to check + * @return {Boolean} `true` if it's a plain object, else `false` + */ export default function isPlainObject(obj) { - return obj ? typeof obj === 'object' && Object.getPrototypeOf(obj) === Object.prototype : false; + return obj ? typeof obj === 'object' + && Object.getPrototypeOf(obj) === Object.prototype : false; } diff --git a/src/utils/mapValues.js b/src/utils/mapValues.js index 29d203cf61..6b2d93e75d 100644 --- a/src/utils/mapValues.js +++ b/src/utils/mapValues.js @@ -1,3 +1,12 @@ +/** + * Given an object, returns a new object with the same keys + * and values generated by running each own enumerable property + * of the object through an `iteratee` function. + * + * @param {Object} obj + * @param {Function} fn - invoked to map the new value + * @return {Object} + */ export default function mapValues(obj, fn) { return Object.keys(obj).reduce((result, key) => { result[key] = fn(obj[key], key); diff --git a/src/utils/pick.js b/src/utils/pick.js index 2c9719c1c0..dfa364fcc4 100644 --- a/src/utils/pick.js +++ b/src/utils/pick.js @@ -1,3 +1,12 @@ +/** + * Given an object, returns a new object with the same keys + * and values filtered by the result of the `iteratee` function. + * + * @param {Object} obj + * @param {Function} fn - invoked to determine whether to discard + * the property or not + * @return {Object} + */ export default function pick(obj, fn) { return Object.keys(obj).reduce((result, key) => { if (fn(obj[key])) { From 39a81070028b8fd0adcb78a92c015a53716163c2 Mon Sep 17 00:00:00 2001 From: Nicola Molinari Date: Mon, 29 Jun 2015 23:49:09 +0200 Subject: [PATCH 06/33] Still more JSDoc --- src/utils/shallowEqual.js | 10 ++++++++++ src/utils/shallowEqualScalar.js | 13 +++++++++++++ 2 files changed, 23 insertions(+) diff --git a/src/utils/shallowEqual.js b/src/utils/shallowEqual.js index f82be71949..2ac60c423d 100644 --- a/src/utils/shallowEqual.js +++ b/src/utils/shallowEqual.js @@ -1,3 +1,13 @@ +/** + * Given two objects, performs equality by iterating through keys + * on an object and returning `false` when any key has values which + * are not strictly equal between `objA` and `objB`. + * Returns `true` when the values of all keys are strictly equal. + * + * @param {Object} objA + * @param {Object} objB + * @return {Boolean} + */ export default function shallowEqual(objA, objB) { if (objA === objB) { return true; diff --git a/src/utils/shallowEqualScalar.js b/src/utils/shallowEqualScalar.js index 2adb8ea85b..62535b52cb 100644 --- a/src/utils/shallowEqualScalar.js +++ b/src/utils/shallowEqualScalar.js @@ -1,3 +1,16 @@ +/** + * Given two objects, performs equality by iterating through keys + * on an object and returning `false` when any key has values which + * are not strictly equal between `objA` and `objB`. + * Returns `true` when the values of all keys are strictly equal. + * + * NOTE: if value is an `Object`, returns `false`. This allows the check + * to be more performant. + * + * @param {Object} objA + * @param {Object} objB + * @return {Boolean} + */ export default function shallowEqualScalar(objA, objB) { if (objA === objB) { return true; From b15f0e6d8b183d5e9c0b0d21a7adf485c092ffbf Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Thu, 18 Jun 2015 13:27:42 -0700 Subject: [PATCH 07/33] Add terminology.md --- docs/terminology.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/terminology.md diff --git a/docs/terminology.md b/docs/terminology.md new file mode 100644 index 0000000000..e69de29bb2 From 41f0bdf1054e8daba70cfe58fd7069c0b9828907 Mon Sep 17 00:00:00 2001 From: Nicola Molinari Date: Thu, 18 Jun 2015 23:54:09 +0200 Subject: [PATCH 08/33] Start FAQ document --- docs/faq.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 docs/faq.md diff --git a/docs/faq.md b/docs/faq.md new file mode 100644 index 0000000000..6f3de57488 --- /dev/null +++ b/docs/faq.md @@ -0,0 +1,3 @@ +**WIP** + +## RR integration From d8168470647245891c4f00be1a3ea57b84aaffd5 Mon Sep 17 00:00:00 2001 From: Nicola Molinari Date: Mon, 29 Jun 2015 22:37:55 +0200 Subject: [PATCH 09/33] Add some JSDoc --- src/middleware/thunk.js | 9 +++++++++ src/utils/bindActionCreators.js | 8 ++++++++ src/utils/getDisplayName.js | 6 ++++++ 3 files changed, 23 insertions(+) diff --git a/src/middleware/thunk.js b/src/middleware/thunk.js index a80a78159c..4500e5aeb0 100644 --- a/src/middleware/thunk.js +++ b/src/middleware/thunk.js @@ -1,3 +1,12 @@ +/** + * Given an action that returns a function, delegates its call + * to the function itself by passing the control of the dispatching, + * otherwise simply dispatch the action. + * In other words, it allows to have async actions. + * + * @param {Function} getState - allow to access the current state + * @return {Function} a new middleware + */ export default function thunkMiddleware(getState) { return (next) => { const recurse = (action) => diff --git a/src/utils/bindActionCreators.js b/src/utils/bindActionCreators.js index f9a81a5a80..e4a757b980 100644 --- a/src/utils/bindActionCreators.js +++ b/src/utils/bindActionCreators.js @@ -1,5 +1,13 @@ import mapValues from '../utils/mapValues'; +/** + * Given a list action creators, wrap them to the `dispatch` function + * in order to be automatically dispatched when invoked. + * + * @param {Object} actionCreators - an object with the action functions + * @param {Function} dispatch + * @return {Object} the given object with wrapped actions + */ export default function bindActionCreators(actionCreators, dispatch) { return mapValues(actionCreators, actionCreator => (...args) => dispatch(actionCreator(...args)) diff --git a/src/utils/getDisplayName.js b/src/utils/getDisplayName.js index 512702c87a..54f3263588 100644 --- a/src/utils/getDisplayName.js +++ b/src/utils/getDisplayName.js @@ -1,3 +1,9 @@ +/** + * Given a React component, return its name to be displayed. + * + * @param {React} Component + * @return {String} the name of the component + */ export default function getDisplayName(Component) { return Component.displayName || Component.name || 'Component'; } From b8308ece061acb3be99e8528f3fca19251a122fb Mon Sep 17 00:00:00 2001 From: Nicola Molinari Date: Mon, 29 Jun 2015 23:18:40 +0200 Subject: [PATCH 10/33] More JSDoc --- src/utils/composeMiddleware.js | 6 ++++++ src/utils/composeStores.js | 10 ++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/utils/composeMiddleware.js b/src/utils/composeMiddleware.js index 596403919d..4f9f47f3e8 100644 --- a/src/utils/composeMiddleware.js +++ b/src/utils/composeMiddleware.js @@ -1,3 +1,9 @@ +/** + * Given a list of middlewares, compose them from left to right. + * + * @param {Array} middlewares - a list of middleware functions + * @return {Function} the combined middleware function + */ export default function composeMiddleware(...middlewares) { return middlewares.reduceRight((composed, m) => m(composed)); } diff --git a/src/utils/composeStores.js b/src/utils/composeStores.js index d8c4420546..ebed3f1baf 100644 --- a/src/utils/composeStores.js +++ b/src/utils/composeStores.js @@ -1,6 +1,16 @@ import mapValues from '../utils/mapValues'; import pick from '../utils/pick'; +/** + * Given a list of stores, maps the state keys to reducer functions. + * The composed store, when invoked, will internally map all the + * results of the computed stores (being effectively the state). + * When the store function is invoked again, it will pass the + * existing state value as initial state. + * + * @param {Object} stores - an object with the store reducer functions + * @return {Function} the composed store function + */ export default function composeStores(stores) { const finalStores = pick(stores, (val) => typeof val === 'function'); return function Composition(atom = {}, action) { From 9de7b4461051b9fcc8a5eda6429a175f95534a1c Mon Sep 17 00:00:00 2001 From: Nicola Molinari Date: Mon, 29 Jun 2015 23:38:09 +0200 Subject: [PATCH 11/33] More JSDoc --- src/utils/createReduxShape.js | 6 ++++++ src/utils/identity.js | 6 ++++++ src/utils/isPlainObject.js | 10 +++++++++- src/utils/mapValues.js | 9 +++++++++ src/utils/pick.js | 9 +++++++++ 5 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/utils/createReduxShape.js b/src/utils/createReduxShape.js index e3795bedd2..d79e1b7f98 100644 --- a/src/utils/createReduxShape.js +++ b/src/utils/createReduxShape.js @@ -1,3 +1,9 @@ +/** + * Define the shape of the `redux` prop, used for props validation. + * + * @param {Object} PropTypes + * @return {Object} + */ export default function createReduxShape(PropTypes) { return PropTypes.shape({ subscribe: PropTypes.func.isRequired, diff --git a/src/utils/identity.js b/src/utils/identity.js index 8c690a8859..f342078921 100644 --- a/src/utils/identity.js +++ b/src/utils/identity.js @@ -1,3 +1,9 @@ +/** + * Returns the first argument provided to it. + * + * @param {*} value + * @return {*} + */ export default function identity(value) { return value; } diff --git a/src/utils/isPlainObject.js b/src/utils/isPlainObject.js index a5845486cf..1a312c5d3f 100644 --- a/src/utils/isPlainObject.js +++ b/src/utils/isPlainObject.js @@ -1,3 +1,11 @@ +/** + * Given an object, checks that it's a plain object. That is, an object + * created by the `Object` constructor or one with a `Prototype` of `null`. + * + * @param {*} obj - the value to check + * @return {Boolean} `true` if it's a plain object, else `false` + */ export default function isPlainObject(obj) { - return obj ? typeof obj === 'object' && Object.getPrototypeOf(obj) === Object.prototype : false; + return obj ? typeof obj === 'object' + && Object.getPrototypeOf(obj) === Object.prototype : false; } diff --git a/src/utils/mapValues.js b/src/utils/mapValues.js index 29d203cf61..6b2d93e75d 100644 --- a/src/utils/mapValues.js +++ b/src/utils/mapValues.js @@ -1,3 +1,12 @@ +/** + * Given an object, returns a new object with the same keys + * and values generated by running each own enumerable property + * of the object through an `iteratee` function. + * + * @param {Object} obj + * @param {Function} fn - invoked to map the new value + * @return {Object} + */ export default function mapValues(obj, fn) { return Object.keys(obj).reduce((result, key) => { result[key] = fn(obj[key], key); diff --git a/src/utils/pick.js b/src/utils/pick.js index 2c9719c1c0..dfa364fcc4 100644 --- a/src/utils/pick.js +++ b/src/utils/pick.js @@ -1,3 +1,12 @@ +/** + * Given an object, returns a new object with the same keys + * and values filtered by the result of the `iteratee` function. + * + * @param {Object} obj + * @param {Function} fn - invoked to determine whether to discard + * the property or not + * @return {Object} + */ export default function pick(obj, fn) { return Object.keys(obj).reduce((result, key) => { if (fn(obj[key])) { From bbca371ab4fc877582a6d650ef0d045553a86168 Mon Sep 17 00:00:00 2001 From: Nicola Molinari Date: Mon, 29 Jun 2015 23:49:09 +0200 Subject: [PATCH 12/33] Still more JSDoc --- src/utils/shallowEqual.js | 10 ++++++++++ src/utils/shallowEqualScalar.js | 13 +++++++++++++ 2 files changed, 23 insertions(+) diff --git a/src/utils/shallowEqual.js b/src/utils/shallowEqual.js index f82be71949..2ac60c423d 100644 --- a/src/utils/shallowEqual.js +++ b/src/utils/shallowEqual.js @@ -1,3 +1,13 @@ +/** + * Given two objects, performs equality by iterating through keys + * on an object and returning `false` when any key has values which + * are not strictly equal between `objA` and `objB`. + * Returns `true` when the values of all keys are strictly equal. + * + * @param {Object} objA + * @param {Object} objB + * @return {Boolean} + */ export default function shallowEqual(objA, objB) { if (objA === objB) { return true; diff --git a/src/utils/shallowEqualScalar.js b/src/utils/shallowEqualScalar.js index 2adb8ea85b..62535b52cb 100644 --- a/src/utils/shallowEqualScalar.js +++ b/src/utils/shallowEqualScalar.js @@ -1,3 +1,16 @@ +/** + * Given two objects, performs equality by iterating through keys + * on an object and returning `false` when any key has values which + * are not strictly equal between `objA` and `objB`. + * Returns `true` when the values of all keys are strictly equal. + * + * NOTE: if value is an `Object`, returns `false`. This allows the check + * to be more performant. + * + * @param {Object} objA + * @param {Object} objB + * @return {Boolean} + */ export default function shallowEqualScalar(objA, objB) { if (objA === objB) { return true; From 5efcc0840133586e305c165d2f34d982915c9a53 Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Sun, 5 Jul 2015 09:17:51 -0700 Subject: [PATCH 13/33] Add higher-order store docs --- docs/higher-order-stores.md | 57 +++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 docs/higher-order-stores.md diff --git a/docs/higher-order-stores.md b/docs/higher-order-stores.md new file mode 100644 index 0000000000..992b84ad39 --- /dev/null +++ b/docs/higher-order-stores.md @@ -0,0 +1,57 @@ +Higher-order stores +=================== + +A higher-order store is a function that turns a store creating function into a new store creating function: + +``` +createStore => createStore' +``` + +Look familiar? It's just like the signature for [middleware](middleware.md), only we're wrapping `createStore()` instead of `dispatch()`. + +Higher-order stores are a pattern for creating composable Redux extensions. They're much the same as higher-order components in React. They can be as simple as `applyMiddleware()`, or as powerful as the Redux Devtools](https://github.com/gaearon/redux-devtools). There's no limit to the kinds of things you can create. + +## How it works + +Let's look at an example. As alluded to above, `applyMiddleware()` is an example of a higher-order store. You use it by wrapping the base `createStore()` function provided by Redux: + +```js +const newCreateStore = applyMiddleware(m1, m2, m3)(createStore); +``` + +Internally, `applyMiddleware()` works by proxying the `dispatch()` method returned by `createStore()`: + +```js +// Implementation has been simplified for the purpose of illustration +export default function applyMiddleware(...middlewares) { + // ...combine middlewares... + + return next => (reducer, initialState) => { + const store = next(reducer, initialState); + return { + ...store, + dispatch: middleware(store.dispatch) + }; + }; +} +``` + +`next` is the next store creating function in the chain — either the return value of another higher-order store, or `createStore()` itself. + +This design allows multiple higher-stores can be combined simply using function composition: + +```js +const newCreateStore = compose( + applyMiddleware(m1, m2, m3), + devTools, + createStore +); +``` + +Now just pass your reducer (and an initial state, if desired) to your new store creating function: + +```js +const store = createStore(reducer, intialState); + + +``` From c7e37e4f95185b4ea162a9395ee7fd964574a6a4 Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Wed, 8 Jul 2015 22:12:21 -0700 Subject: [PATCH 14/33] Split README into separate docs --- docs/design-goals.md | 17 +++++++ docs/examples.md | 39 +++++++++++++++ docs/getting-started.md | 71 ++++++++++++++++++++++++++ docs/higher-order-stores.md | 4 +- docs/middleware.md | 54 +++++++++++++++++++- docs/react.md | 99 +++++++++++++++++++++++++++++++++++++ docs/store.md | 27 ++++++++++ docs/universal-rendering.md | 16 ++++++ 8 files changed, 324 insertions(+), 3 deletions(-) create mode 100644 docs/design-goals.md create mode 100644 docs/examples.md create mode 100644 docs/getting-started.md create mode 100644 docs/react.md create mode 100644 docs/store.md create mode 100644 docs/universal-rendering.md diff --git a/docs/design-goals.md b/docs/design-goals.md new file mode 100644 index 0000000000..f583ff0a88 --- /dev/null +++ b/docs/design-goals.md @@ -0,0 +1,17 @@ +### Philosophy & Design Goals + +* You shouldn't need a book on functional programming to use Redux. +* Everything (Stores, Action Creators, configuration) is hot reloadable. +* Preserves the benefits of Flux, but adds other nice properties thanks to its functional nature. +* Prevents some of the anti-patterns common in Flux code. +* Works great in [universal (aka “isomorphic”)](https://medium.com/@mjackson/universal-javascript-4761051b7ae9) apps because it doesn't use singletons and the data can be rehydrated. +* Doesn't care how you store your data: you may use JS objects, arrays, ImmutableJS, etc. +* Under the hood, it keeps all your data in a tree, but you don't need to think about it. +* Lets you efficiently subscribe to finer-grained updates than individual Stores. +* Provides hooks for powerful devtools (e.g. time travel, record/replay) to be implementable without user buy-in. +* Provides extension points so it's easy to [support promises](https://github.com/gaearon/redux/issues/99#issuecomment-112212639) or [generate constants](https://gist.github.com/skevy/8a4ffc3cfdaf5fd68739) outside the core. +* No wrapper calls in your stores and actions. Your stuff is your stuff. +* It's super easy to test things in isolation without mocks. +* You can use “flat” Stores, or [compose and reuse Stores](https://gist.github.com/gaearon/d77ca812015c0356654f) just like you compose Components. +* The API surface area is minimal. +* Have I mentioned hot reloading yet? diff --git a/docs/examples.md b/docs/examples.md new file mode 100644 index 0000000000..4a619718db --- /dev/null +++ b/docs/examples.md @@ -0,0 +1,39 @@ +## Examples + +### Simple Examples + +Redux is distributed with a Counter and a TodoMVC example in its source code. + +First, clone the repo: + +``` +git clone https://github.com/gaearon/redux.git +cd redux +``` + +Run the Counter example: + +``` +cd redux/examples/counter +npm install +npm start +``` + +Run the TodoMVC example: + +``` +cd ../todomvc +npm install +npm start +``` + +### Async and Universal Examples with Routing + +These async and [universal (aka “isomorphic”)](https://medium.com/@mjackson/universal-javascript-4761051b7ae9) examples using React Router should help you get started: + +* [redux-react-router-async-example](https://github.com/emmenko/redux-react-router-async-example): Work in progress. Semi-official. Only the client side. Uses React Router. +* [react-redux-universal-hot-example](https://github.com/erikras/react-redux-universal-hot-example): Universal. Uses React Router. +* [redux-example](https://github.com/quangbuule/redux-example): Universal. Uses Immutable, React Router. +* [isomorphic-counter-example](https://github.com/khtdr/redux-react-koa-isomorphic-counter-example): Universal. A bare-bone implentation of the [counter example app](https://github.com/gaearon/redux/tree/master/examples/counter). Uses promises-middleware to interact with API via Koa on the server. + +Don’t be shy, add your own! diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 0000000000..6f50873bf2 --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,71 @@ +### Actions + +```js +// Still using constants... +import { INCREMENT_COUNTER, DECREMENT_COUNTER } from '../constants/ActionTypes'; + +// But action creators are pure functions returning actions +export function increment() { + return { + type: INCREMENT_COUNTER + }; +} + +export function decrement() { + return { + type: DECREMENT_COUNTER + }; +} + +// Can also be async if you return a function +export function incrementAsync() { + return dispatch => { + setTimeout(() => { + // Yay! Can invoke sync or async actions with `dispatch` + dispatch(increment()); + }, 1000); + }; +} + + +// Could also read state of a store in the callback form +export function incrementIfOdd() { + return (dispatch, getState) => { + const { counter } = getState(); + + if (counter % 2 === 0) { + return; + } + + dispatch(increment()); + }; +} +``` + +### Stores +```js +// ... too, use constants +import { INCREMENT_COUNTER, DECREMENT_COUNTER } from '../constants/ActionTypes'; + +// what's important is that Store is a pure function, +// and you can write it anyhow you like. + +// the Store signature is (state, action) => state, +// and the state shape is up to you: you can use primitives, +// objects, arrays, or even ImmutableJS objects. + +export default function counter(state = 0, action) { + // this function returns the new state when an action comes + switch (action.type) { + case INCREMENT_COUNTER: + return state + 1; + case DECREMENT_COUNTER: + return state - 1; + default: + return state; + } + + // BUT THAT'S A SWITCH STATEMENT! + // Right. If you hate 'em, see the FAQ below. +} +``` diff --git a/docs/higher-order-stores.md b/docs/higher-order-stores.md index 992b84ad39..25c3700cdc 100644 --- a/docs/higher-order-stores.md +++ b/docs/higher-order-stores.md @@ -7,9 +7,9 @@ A higher-order store is a function that turns a store creating function into a n createStore => createStore' ``` -Look familiar? It's just like the signature for [middleware](middleware.md), only we're wrapping `createStore()` instead of `dispatch()`. +Look familiar? It's just like the signature for [middleware](middleware.md), only we're wrapping `createStore()` instead of `dispatch()`. Like middleware, the key feature of higher-order stores is that they are composable. -Higher-order stores are a pattern for creating composable Redux extensions. They're much the same as higher-order components in React. They can be as simple as `applyMiddleware()`, or as powerful as the Redux Devtools](https://github.com/gaearon/redux-devtools). There's no limit to the kinds of things you can create. +Higher-order stores are much the same as higher-order components in React. They can be as simple as `applyMiddleware()`, or as powerful as the Redux Devtools](https://github.com/gaearon/redux-devtools). There's no limit to the kinds of extensions you can create. ## How it works diff --git a/docs/middleware.md b/docs/middleware.md index 7de0ee8bce..93acf73a8b 100644 --- a/docs/middleware.md +++ b/docs/middleware.md @@ -1,4 +1,56 @@ -# Middleware +Middleware +========== + +A middleware in Redux is a function that turns a dispatching function into a new dispatching function: + +``` +dispatch => dispatch' +``` + +The key feature of middleware is that it is composable. Multiple middleware can be combined together, where each middleware requires no knowledge of the what comes before or after it in the chain. + +Usage +===== + +To enable middleware in your Redux app, use `applyMiddleware()`. + +### `applyMiddleware(...middlewares)` + +This function returns a [higher-order store](higher-order store). You don't need to worry about that if you're not interested — here's how you use it: + +```js +const store = applyMiddleware(thunk, promise, observable)(createStore)(reducer); +``` + +Yes, you read that correctly. If this looks strange to you, it may help to break the process down into multiple steps: + +```js +const newCreateStore = applyMiddleware(thunk, promise, observable)(createStore); +const store = newCreateStore(reducer); +``` + +If you + +How it works +============ + +```js +const newDispatch = thunk(promise(observable(dispatch))); +// Or +const newDispatch = compose(thunk, promise, observable, dispatch); +``` + +`compose` performs function composition. It is the same as `compose()` in underscore or lodash. + +You can also use `composeMiddleware()`, which is similar to `compose()` except instead of creating a dispatching function, it creates a middleware function: + +```js +const middleware = composeMiddleware(thunk, promise, observable); +const newDispatch = compose(middleware, dispatch); +// Or simply +const newDispatch = compose(dispatch); +``` + A middleware is a function that wraps the `dispatch()` method, or another middleware. For example: diff --git a/docs/react.md b/docs/react.md new file mode 100644 index 0000000000..600cefd850 --- /dev/null +++ b/docs/react.md @@ -0,0 +1,99 @@ +### Components + +#### Dumb Components + +```js +// The dumb component receives everything using props: +import React, { PropTypes } from 'react'; + +export default class Counter { + static propTypes = { + increment: PropTypes.func.isRequired, + decrement: PropTypes.func.isRequired, + counter: PropTypes.number.isRequired + }; + + render() { + const { increment, decrement, counter } = this.props; + return ( +

+ Clicked: {counter} times + {' '} + + {' '} + +

+ ); + } +} +``` + +#### Smart Components + +```js +// The smart component may observe stores using ``, +// and bind actions to the dispatcher with `bindActionCreators`. + +import React from 'react'; +import { bindActionCreators } from 'redux'; +import { Connector } from 'redux/react'; +import Counter from '../components/Counter'; +import * as CounterActions from '../actions/CounterActions'; + +// You can optionally specify `select` for finer-grained subscriptions +// and retrieval. Only when the return value is shallowly different, +// will the child component be updated. +function select(state) { + return { counter: state.counter }; +} + +export default class CounterApp { + render() { + return ( + + {({ counter, dispatch }) => + /* Yes this is child as a function. */ + + } + + ); + } +} +``` + +#### Decorators + +The `@connect` decorator lets you create smart components less verbosely: + +```js +import React from 'react'; +import { bindActionCreators } from 'redux'; +import { connect } from 'redux/react'; +import Counter from '../components/Counter'; +import * as CounterActions from '../actions/CounterActions'; + +@connect(state => ({ + counter: state.counter +})) +export default class CounterApp { + render() { + const { counter, dispatch } = this.props; + // Instead of `bindActionCreators`, you may also pass `dispatch` as a prop + // to your component and call `dispatch(CounterActions.increment())` + return ( + + ); + } +} +``` + +### React Native + +To use Redux with React Native, just replace imports from `redux/react` with `redux/react-native`: + +```js +import { bindActionCreators } from 'redux'; +import { Provider, Connector } from 'redux/react-native'; +``` diff --git a/docs/store.md b/docs/store.md new file mode 100644 index 0000000000..92d42a093d --- /dev/null +++ b/docs/store.md @@ -0,0 +1,27 @@ +### Initializing Redux + +The simplest way to initialize a Redux instance is to give it an object whose values are your Store functions, and whose keys are their names. You may `import *` from the file with all your Store definitions to obtain such an object: + +```js +import { createRedux } from 'redux'; +import { Provider } from 'redux/react'; +import * as stores from '../stores/index'; + +const redux = createRedux(stores); +``` + +Then pass `redux` as a prop to `` component in the root component of your app, and you're all set: + +```js +export default class App { + render() { + return ( + + {() => + + } + + ); + } +} +``` diff --git a/docs/universal-rendering.md b/docs/universal-rendering.md new file mode 100644 index 0000000000..5099933e35 --- /dev/null +++ b/docs/universal-rendering.md @@ -0,0 +1,16 @@ +### Running the same code on client and server + +The `redux` instance returned by `createRedux` also has the `dispatch(action)`, `subscribe()` and `getState()` methods that you may call outside the React components. + +You may optionally specify the initial state as the second argument to `createRedux`. This is useful for hydrating the state you received from running Redux on the server: + +```js +// server +const redux = createRedux(stores); +redux.dispatch(MyActionCreators.doSomething()); // fire action creators to fill the state +const state = redux.getState(); // somehow pass this state to the client + +// client +const initialState = window.STATE_FROM_SERVER; +const redux = createRedux(stores, initialState); +``` From 01b8e41b92530ffba067088d469c25b34d1f79b2 Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Wed, 8 Jul 2015 22:15:00 -0700 Subject: [PATCH 15/33] FAQ --- README.md | 453 ------------------------------------ docs/faq.md | 102 +++++++- docs/higher-order-stores.md | 2 +- 3 files changed, 101 insertions(+), 456 deletions(-) diff --git a/README.md b/README.md index 11be17b5b6..8d3d20e862 100644 --- a/README.md +++ b/README.md @@ -7,65 +7,11 @@ redux Atomic Flux with hot reloading. -**The API is likely to change a few times before we reach 1.0.**
-**Its [surface area](http://www.youtube.com/watch?v=4anAwXYqLG8) is minimal so you can try it in production and report any issues.** - -**You can track the [new docs](https://github.com/gaearon/redux/pull/140) and the [1.0 API and terminology changes](https://github.com/gaearon/redux/pull/195).** - - -# Table of Contents - -- [Why another Flux framework?](#why-another-flux-framework) - - [Philosophy & Design Goals](#philosophy--design-goals) -- [The Talk](#the-talk) -- [Demo](#demo) -- [Examples](#examples) - - [Simple Examples](#simple-examples) - - [Async and Universal Examples with Routing](#async-and-universal-examples-with-routing) -- [What does it look like?](#what-does-it-look-like) - - [Actions](#actions) - - [Stores](#stores) - - [Components](#components) - - [Dumb Components](#dumb-components) - - [Smart Components](#smart-components) - - [Decorators](#decorators) - - [React Native](#react-native) - - [Initializing Redux](#initializing-redux) - - [Running the same code on client and server](#running-the-same-code-on-client-and-server) - - [Additional customization](#additional-customization) -- [FAQ](#faq) - - [How does hot reloading work?](#how-does-hot-reloading-work) - - [Can I use this in production?](#can-i-use-this-in-production) - - [How do I do async?](#how-do-i-do-async) - - [But there are switch statements!](#but-there-are-switch-statements) - - [What about `waitFor`?](#what-about-waitfor) - - [My views aren't updating!](#my-views-arent-updating) - - [How do Stores, Actions and Components interact?](#how-do-stores-actions-and-components-interact) -- [Discussion](#discussion) -- [Inspiration and Thanks](#inspiration-and-thanks) ## Why another Flux framework? Read **[The Evolution of Flux Frameworks](https://medium.com/@dan_abramov/the-evolution-of-flux-frameworks-6c16ad26bb31)** for some context. -### Philosophy & Design Goals - -* You shouldn't need a book on functional programming to use Redux. -* Everything (Stores, Action Creators, configuration) is hot reloadable. -* Preserves the benefits of Flux, but adds other nice properties thanks to its functional nature. -* Prevents some of the anti-patterns common in Flux code. -* Works great in [universal (aka “isomorphic”)](https://medium.com/@mjackson/universal-javascript-4761051b7ae9) apps because it doesn't use singletons and the data can be rehydrated. -* Doesn't care how you store your data: you may use JS objects, arrays, ImmutableJS, etc. -* Under the hood, it keeps all your data in a tree, but you don't need to think about it. -* Lets you efficiently subscribe to finer-grained updates than individual Stores. -* Provides hooks for powerful devtools (e.g. time travel, record/replay) to be implementable without user buy-in. -* Provides extension points so it's easy to [support promises](https://github.com/gaearon/redux/issues/99#issuecomment-112212639) or [generate constants](https://gist.github.com/skevy/8a4ffc3cfdaf5fd68739) outside the core. -* No wrapper calls in your stores and actions. Your stuff is your stuff. -* It's super easy to test things in isolation without mocks. -* You can use “flat” Stores, or [compose and reuse Stores](https://gist.github.com/gaearon/d77ca812015c0356654f) just like you compose Components. -* The API surface area is minimal. -* Have I mentioned hot reloading yet? - ## The Talk Redux was demoed together with **[React Hot Loader](https://github.com/gaearon/react-hot-loader)** at React Europe. @@ -75,405 +21,6 @@ Watch **[Dan Abramov's talk on Hot Reloading with Time Travel](https://www.youtu -## Examples - -### Simple Examples - -Redux is distributed with a Counter and a TodoMVC example in its source code. - -First, clone the repo: - -``` -git clone https://github.com/gaearon/redux.git -cd redux -``` - -Run the Counter example: - -``` -cd redux/examples/counter -npm install -npm start -``` - -Run the TodoMVC example: - -``` -cd ../todomvc -npm install -npm start -``` - -### Async and Universal Examples with Routing - -These async and [universal (aka “isomorphic”)](https://medium.com/@mjackson/universal-javascript-4761051b7ae9) examples using React Router should help you get started: - -* [redux-react-router-async-example](https://github.com/emmenko/redux-react-router-async-example): Work in progress. Semi-official. Only the client side. Uses React Router. -* [react-redux-universal-hot-example](https://github.com/erikras/react-redux-universal-hot-example): Universal. Uses React Router. -* [redux-example](https://github.com/quangbuule/redux-example): Universal. Uses Immutable, React Router. -* [isomorphic-counter-example](https://github.com/khtdr/redux-react-koa-isomorphic-counter-example): Universal. A bare-bone implentation of the [counter example app](https://github.com/gaearon/redux/tree/master/examples/counter). Uses promises-middleware to interact with API via Koa on the server. - -Don’t be shy, add your own! - -## What does it look like? - -### Actions - -```js -// Still using constants... -import { INCREMENT_COUNTER, DECREMENT_COUNTER } from '../constants/ActionTypes'; - -// But action creators are pure functions returning actions -export function increment() { - return { - type: INCREMENT_COUNTER - }; -} - -export function decrement() { - return { - type: DECREMENT_COUNTER - }; -} - -// Can also be async if you return a function -export function incrementAsync() { - return dispatch => { - setTimeout(() => { - // Yay! Can invoke sync or async actions with `dispatch` - dispatch(increment()); - }, 1000); - }; -} - - -// Could also read state of a store in the callback form -export function incrementIfOdd() { - return (dispatch, getState) => { - const { counter } = getState(); - - if (counter % 2 === 0) { - return; - } - - dispatch(increment()); - }; -} -``` - -### Stores -```js -// ... too, use constants -import { INCREMENT_COUNTER, DECREMENT_COUNTER } from '../constants/ActionTypes'; - -// what's important is that Store is a pure function, -// and you can write it anyhow you like. - -// the Store signature is (state, action) => state, -// and the state shape is up to you: you can use primitives, -// objects, arrays, or even ImmutableJS objects. - -export default function counter(state = 0, action) { - // this function returns the new state when an action comes - switch (action.type) { - case INCREMENT_COUNTER: - return state + 1; - case DECREMENT_COUNTER: - return state - 1; - default: - return state; - } - - // BUT THAT'S A SWITCH STATEMENT! - // Right. If you hate 'em, see the FAQ below. -} -``` - -### Components - -#### Dumb Components - -```js -// The dumb component receives everything using props: -import React, { PropTypes } from 'react'; - -export default class Counter { - static propTypes = { - increment: PropTypes.func.isRequired, - decrement: PropTypes.func.isRequired, - counter: PropTypes.number.isRequired - }; - - render() { - const { increment, decrement, counter } = this.props; - return ( -

- Clicked: {counter} times - {' '} - - {' '} - -

- ); - } -} -``` - -#### Smart Components - -```js -// The smart component may observe stores using ``, -// and bind actions to the dispatcher with `bindActionCreators`. - -import React from 'react'; -import { bindActionCreators } from 'redux'; -import { Connector } from 'redux/react'; -import Counter from '../components/Counter'; -import * as CounterActions from '../actions/CounterActions'; - -// You can optionally specify `select` for finer-grained subscriptions -// and retrieval. Only when the return value is shallowly different, -// will the child component be updated. -function select(state) { - return { counter: state.counter }; -} - -export default class CounterApp { - render() { - return ( - - {({ counter, dispatch }) => - /* Yes this is child as a function. */ - - } - - ); - } -} -``` - -#### Decorators - -The `@connect` decorator lets you create smart components less verbosely: - -```js -import React from 'react'; -import { bindActionCreators } from 'redux'; -import { connect } from 'redux/react'; -import Counter from '../components/Counter'; -import * as CounterActions from '../actions/CounterActions'; - -@connect(state => ({ - counter: state.counter -})) -export default class CounterApp { - render() { - const { counter, dispatch } = this.props; - // Instead of `bindActionCreators`, you may also pass `dispatch` as a prop - // to your component and call `dispatch(CounterActions.increment())` - return ( - - ); - } -} -``` - -### React Native - -To use Redux with React Native, just replace imports from `redux/react` with `redux/react-native`: - -```js -import { bindActionCreators } from 'redux'; -import { Provider, Connector } from 'redux/react-native'; -``` - -### Initializing Redux - -The simplest way to initialize a Redux instance is to give it an object whose values are your Store functions, and whose keys are their names. You may `import *` from the file with all your Store definitions to obtain such an object: - -```js -import { createRedux } from 'redux'; -import { Provider } from 'redux/react'; -import * as stores from '../stores/index'; - -const redux = createRedux(stores); -``` - -Then pass `redux` as a prop to `` component in the root component of your app, and you're all set: - -```js -export default class App { - render() { - return ( - - {() => - - } - - ); - } -} -``` - -### Running the same code on client and server - -The `redux` instance returned by `createRedux` also has the `dispatch(action)`, `subscribe()` and `getState()` methods that you may call outside the React components. - -You may optionally specify the initial state as the second argument to `createRedux`. This is useful for hydrating the state you received from running Redux on the server: - -```js -// server -const redux = createRedux(stores); -redux.dispatch(MyActionCreators.doSomething()); // fire action creators to fill the state -const state = redux.getState(); // somehow pass this state to the client - -// client -const initialState = window.STATE_FROM_SERVER; -const redux = createRedux(stores, initialState); -``` - -### Additional customization - -There is also a longer way to do the same thing, if you need additional customization. - -This: - -```js -import { createRedux } from 'redux'; -import * as stores from '../stores/index'; - -const redux = createRedux(stores); -``` - -is in fact a shortcut for this: - -```js -import { createRedux, createDispatcher, composeStores } from 'redux'; -import thunkMiddleware from 'redux/lib/middleware/thunk'; -import * as stores from '../stores/index'; - -// Compose all your Stores into a single Store function with `composeStores`: -const store = composeStores(stores); - -// Create a Dispatcher function for your composite Store: -const dispatcher = createDispatcher( - store, - getState => [thunkMiddleware(getState)] // Pass the default middleware -); - -// Create a Redux instance using the dispatcher function: -const redux = createRedux(dispatcher); -``` - -Why would you want to write it longer? Maybe you're an advanced user and want to provide a custom Dispatcher function, or maybe you have a different idea of how to compose your Stores (or you're satisfied with a single Store). Redux lets you do all of this. - -`createDispatcher()` also gives you the ability to specify middleware -- for example, to add support for promises. [Learn more](https://github.com/gaearon/redux/blob/master/docs/middleware.md) about how to create and use middleware in Redux. - -When in doubt, use the shorter option! - -## FAQ - -### How does hot reloading work? - -* http://webpack.github.io/docs/hot-module-replacement.html -* http://gaearon.github.io/react-hot-loader/ -* Literally that's it. Redux is fully driven by component props, so it works on top of React Hot Loader. - -### Can I use this in production? - -Yep. People already do that although I warned them! The API surface is minimal so migrating to 1.0 API when it comes out won't be difficult. Let us know about any issues. - -### How do I do async? - -There's already a built-in way of doing async action creators: - -```js -// Can also be async if you return a function -export function incrementAsync() { - return dispatch => { - setTimeout(() => { - // Yay! Can invoke sync or async actions with `dispatch` - dispatch(increment()); - }, 1000); - }; -} -``` - -It's also easy to implement support for returning Promises or Observables with a custom middleware. [See an example of a custom Promise middleware.](https://github.com/gaearon/redux/issues/99#issuecomment-112212639) - -### But there are switch statements! - -`(state, action) => state` is as simple as a Store can get. You are free to implement your own `createStore`: - -```js -export default function createStore(initialState, handlers) { - return (state = initialState, action) => - handlers[action.type] ? - handlers[action.type](state, action) : - state; -} -``` - -and use it for your Stores: - -```js -export default createStore(0, { - [INCREMENT_COUNTER]: x => x + 1, - [DECREMENT_COUNTER]: x => x - 1 -}); -``` - -It's all just functions. -Fancy stuff like generating stores from handler maps, or generating action creator constants, should be in userland. -Redux has no opinion on how you do this in your project. - -See also [this gist](https://gist.github.com/skevy/8a4ffc3cfdaf5fd68739) for an example implementation of action constant generation. - -### What about `waitFor`? - -I wrote a lot of vanilla Flux code and my only use case for it was to avoid emitting a change before a related Store consumes the action. This doesn't matter in Redux because the change is only emitted after *all* Stores have consumed the action. - -If several of your Stores want to read data from each other and depend on each other, it's a sign that they should've been a single Store instead. [See this discussion on how `waitFor` can be replaced by the composition of stateless Stores.](https://gist.github.com/gaearon/d77ca812015c0356654f) - -### My views aren't updating! - -Redux makes a hard assumption that you never mutate the state passed to you. It's easy! For example, instead of - -```js -function (state, action) { - state.isAuthenticated = true; - state.email = action.email; - return state; -} -``` - -you should write - -```js -function (state, action) { - return { - ...state, - isAuthenticated: true, - email: action.email - }; -} -``` - -[Read more](https://github.com/sebmarkbage/ecmascript-rest-spread) about the spread properties ES7 proposal. - -### How do Stores, Actions and Components interact? - -Action creators are just pure functions so they don't interact with anything. Components need to call `dispatch(action)` (or use `bindActionCreators` that wraps it) to dispatch an action *returned* by the action creator. - -Stores are just pure functions too so they don't need to be “registered” in the traditional sense, and you can't subscribe to them directly. They're just descriptions of how data transforms. So in that sense they don't “interact” with anything either, they just exist, and are used by the dispatcher for computation of the next state. - -Now, the dispatcher is more interesting. You pass all the Stores to it, and it composes them into a single Store function that it uses for computation. The dispatcher is also a pure function, and it is passed as configuration to `createRedux`, the only stateful thing in Redux. By default, the default dispatcher is used, so if you call `createRedux(stores)`, it is created implicitly. - -To sum it up: there is a Redux instance at the root of your app. It binds everything together. It accepts a dispatcher (which itself accepts Stores), it holds the state, and it knows how to turn actions into state updates. Everything else (components, for example) subscribes to the Redux instance. If something wants to dispatch an action, they need to do it on the Redux instance. `Connector` is a handy shortcut for subscribing to a slice of the Redux instance's state and injecting `dispatch` into your components, but you don't have to use it. - -There is no other “interaction” in Redux. ## Discussion diff --git a/docs/faq.md b/docs/faq.md index 6f3de57488..6bfc1dd778 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -1,3 +1,101 @@ -**WIP** +## FAQ -## RR integration +### How does hot reloading work? + +* http://webpack.github.io/docs/hot-module-replacement.html +* http://gaearon.github.io/react-hot-loader/ +* Literally that's it. Redux is fully driven by component props, so it works on top of React Hot Loader. + +### Can I use this in production? + +Yep. People already do that although I warned them! The API surface is minimal so migrating to 1.0 API when it comes out won't be difficult. Let us know about any issues. + +### How do I do async? + +There's already a built-in way of doing async action creators: + +```js +// Can also be async if you return a function +export function incrementAsync() { + return dispatch => { + setTimeout(() => { + // Yay! Can invoke sync or async actions with `dispatch` + dispatch(increment()); + }, 1000); + }; +} +``` + +It's also easy to implement support for returning Promises or Observables with a custom middleware. [See an example of a custom Promise middleware.](https://github.com/gaearon/redux/issues/99#issuecomment-112212639) + +### But there are switch statements! + +`(state, action) => state` is as simple as a Store can get. You are free to implement your own `createStore`: + +```js +export default function createStore(initialState, handlers) { + return (state = initialState, action) => + handlers[action.type] ? + handlers[action.type](state, action) : + state; +} +``` + +and use it for your Stores: + +```js +export default createStore(0, { + [INCREMENT_COUNTER]: x => x + 1, + [DECREMENT_COUNTER]: x => x - 1 +}); +``` + +It's all just functions. +Fancy stuff like generating stores from handler maps, or generating action creator constants, should be in userland. +Redux has no opinion on how you do this in your project. + +See also [this gist](https://gist.github.com/skevy/8a4ffc3cfdaf5fd68739) for an example implementation of action constant generation. + +### What about `waitFor`? + +I wrote a lot of vanilla Flux code and my only use case for it was to avoid emitting a change before a related Store consumes the action. This doesn't matter in Redux because the change is only emitted after *all* Stores have consumed the action. + +If several of your Stores want to read data from each other and depend on each other, it's a sign that they should've been a single Store instead. [See this discussion on how `waitFor` can be replaced by the composition of stateless Stores.](https://gist.github.com/gaearon/d77ca812015c0356654f) + +### My views aren't updating! + +Redux makes a hard assumption that you never mutate the state passed to you. It's easy! For example, instead of + +```js +function (state, action) { + state.isAuthenticated = true; + state.email = action.email; + return state; +} +``` + +you should write + +```js +function (state, action) { + return { + ...state, + isAuthenticated: true, + email: action.email + }; +} +``` + +[Read more](https://github.com/sebmarkbage/ecmascript-rest-spread) about the spread properties ES7 proposal. + +### How do Stores, Actions and Components interact? + +Action creators are just pure functions so they don't interact with anything. Components need to call `dispatch(action)` (or use `bindActionCreators` that wraps it) to dispatch an action *returned* by the action creator. + +Stores are just pure functions too so they don't need to be “registered” in the traditional sense, and you can't subscribe to them directly. They're just descriptions of how data transforms. So in that sense they don't “interact” with anything either, they just exist, and are used by the dispatcher for computation of the next state. + +Now, the dispatcher is more interesting. You pass all the Stores to it, and it composes them into a single Store function that it uses for computation. The dispatcher is also a pure function, and it is passed as configuration to `createRedux`, the only stateful thing in Redux. By default, the default dispatcher is used, so if you call `createRedux(stores)`, it is created implicitly. + +To sum it up: there is a Redux instance at the root of your app. It binds everything together. It accepts a dispatcher (which itself accepts Stores), it holds the state, and it knows how to turn actions into state updates. Everything else (components, for example) subscribes to the Redux instance. If something wants to dispatch an action, they need to do it on the Redux instance. `Connector` is a handy shortcut for subscribing to a slice of the Redux instance's state and injecting `dispatch` into your components, but you don't have to use it. + +There is no other “interaction” in Redux. diff --git a/docs/higher-order-stores.md b/docs/higher-order-stores.md index 25c3700cdc..5da6458d35 100644 --- a/docs/higher-order-stores.md +++ b/docs/higher-order-stores.md @@ -9,7 +9,7 @@ createStore => createStore' Look familiar? It's just like the signature for [middleware](middleware.md), only we're wrapping `createStore()` instead of `dispatch()`. Like middleware, the key feature of higher-order stores is that they are composable. -Higher-order stores are much the same as higher-order components in React. They can be as simple as `applyMiddleware()`, or as powerful as the Redux Devtools](https://github.com/gaearon/redux-devtools). There's no limit to the kinds of extensions you can create. +Higher-order stores are much the same as higher-order components in React. They can be as simple as `applyMiddleware()`, or as powerful as the [Redux Devtools](https://github.com/gaearon/redux-devtools). There's no limit to the kinds of extensions you can create. ## How it works From 9522aed2ef32a2654f121a2bd5beee8f5cd4a87b Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Thu, 9 Jul 2015 00:24:55 -0700 Subject: [PATCH 16/33] Add types and terminology --- docs/higher-order-stores.md | 14 ++++- docs/testing.md | 1 + docs/types-and-terminology.md | 103 ++++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+), 3 deletions(-) create mode 100644 docs/testing.md create mode 100644 docs/types-and-terminology.md diff --git a/docs/higher-order-stores.md b/docs/higher-order-stores.md index 5da6458d35..cbd76a8c84 100644 --- a/docs/higher-order-stores.md +++ b/docs/higher-order-stores.md @@ -7,7 +7,7 @@ A higher-order store is a function that turns a store creating function into a n createStore => createStore' ``` -Look familiar? It's just like the signature for [middleware](middleware.md), only we're wrapping `createStore()` instead of `dispatch()`. Like middleware, the key feature of higher-order stores is that they are composable. +Look familiar? It's like the signature for [middleware](middleware.md), only we're wrapping `createStore()` instead of `dispatch()`. Like middleware, the key feature of higher-order stores is that they are composable. Higher-order stores are much the same as higher-order components in React. They can be as simple as `applyMiddleware()`, or as powerful as the [Redux Devtools](https://github.com/gaearon/redux-devtools). There's no limit to the kinds of extensions you can create. @@ -38,7 +38,7 @@ export default function applyMiddleware(...middlewares) { `next` is the next store creating function in the chain — either the return value of another higher-order store, or `createStore()` itself. -This design allows multiple higher-stores can be combined simply using function composition: +This design allows for multiple higher-order stores to be used together using function composition. ```js const newCreateStore = compose( @@ -51,7 +51,15 @@ const newCreateStore = compose( Now just pass your reducer (and an initial state, if desired) to your new store creating function: ```js -const store = createStore(reducer, intialState); +const store = newCreateStore(reducer, intialState); ``` + +## Creating higher-order stores + +The signature of a higher-order store looks like this: + +## Middleware versus higher-order stores + +Middleware and higher-order stores are conceptually similar. Both wrap around the store interface to modify its behavior in a composable way. The difference is that middleware is exclusively concerned with modifying the behavior of `dispatch()`, whereas higher-order stores can modify any part of the store interface. diff --git a/docs/testing.md b/docs/testing.md new file mode 100644 index 0000000000..464090415c --- /dev/null +++ b/docs/testing.md @@ -0,0 +1 @@ +# TODO diff --git a/docs/types-and-terminology.md b/docs/types-and-terminology.md new file mode 100644 index 0000000000..17cee0527c --- /dev/null +++ b/docs/types-and-terminology.md @@ -0,0 +1,103 @@ +Types and terminology +===================== + +This is a glossary of the core terms in Redux, along with their type signatures. Types documented using [Flow notation](http://flowtype.org/docs/quick-reference.html#_). + +### State + +```js +type State = any +``` + +**State** is a broad term, but in the Redux API it usually refers to the single state value that is managed by the store and returned by `getState()`. It is composed of nested properties and represents the entire state of the Redux store. By convention, the top-level state is an an object or some other key-value collection like a Map, but technically it can be any type. + +### Action + +```js +type Action = Object +``` + +An **action** is a payload of information that is used to accumulate state. The application uses actions to send data to the store. By convention, they contain a `type` property which indicates the nature of the action being dispatched. + +See also **intermediate actions** below. + +### Dispatching function + +```js +type Dispatch = (a: Action | IntermediateAction) => any +``` + +A **dispatching function** (or simply **dispatch function**) is a function that accepts an action or an intermediate action; it then may or may not dispatch one or more actions to the store. + +We must distinguish between dispatching functions in general and the base dispatch function provided by the Store interface. The base dispatch function *always* sends an action to the store's reducer, along with the previous state returned by the store, to calculate a new state. However, the application will rarely call the base dispatch function directly — usually, the base dispatch function is wrapped by middleware. See below for more information. + +### Reducer + +```js +type Reducer = (state: S, action: A) => S +``` + +A **reducer** or **reducing function** is a function that accepts an accumulation and a value and returns a new accumulation. They are used to reduce a collection of values down to a single value. Reducers are not unique to Redux — they are a fundamental concept in functional programming. Even most non-functional languages, like JavaScript, have a built-in API for reducing. (In JavaScript, it's `Array.prototype.reduce()`.) + +In Redux, the accumulated value is the state object, and the values being accumulated are actions. Reducers calculate a new state given the previous state and an action. They must be *pure functions* — functions that return the exact same output for given inputs. They should also be free of side-effects. This is what enables exciting features like hot reloading and time travel. + +Reducers are the most important concept in Redux. + +### Action creator + +```js +type ActionCreator = (...args) => Action | IntermediateAction +``` + +An action creator is, quite simply, a function that creates an action. Do not confuse the two terms — again, an action is a payload of information, and an action creator is a factory that creates them. + +### Intermediate action + +```js +type IntermediateAction = any +``` + +An *intermediate action* is a value that is sent to a dispatching function, but is not yet ready for consumption by the reducer; it will be transformed by middleware before being sent to the base `dispatch()` function. Intermediate actions are often asynchronous primitives, like a promise or a thunk, which are not dispatched themselves, but trigger dispatches once an operation has completed. + +### Middleware + +```js +type Middleware = ({ dispatch: Dispatch, getState: () => State }) => next: Dispatch => Dispatch +``` + +A middleware is a higher-order function that composes a dispatch function to return a new dispatch function. + +- The outermost function receives an object of methods which are a subset of the Store interface: `dispatch()` and `getState()`. This gives the inner function access to these methods. +- That returns another function, which receives a dispatch function. This dispatch function is not necessarily the same as the base dispatch function passed to the outermost function — it is the next dispatch function in the middleware chain. +- The innermost function is a dispatch function. It receives an action, and can either call the next dispatch function in the chain, or call the base dispatch function to restart the chain. It can call either function asynchronously and multiple times, or it can call nothing at all. A no-op middleware should synchronously call `next(action)`. + +Middleware is composable using function composition. + +### Store + +```js +type Store = { dispatch: Dispatch, getState: State, subscribe: Function, getReducer: Reducer, replaceReducer: void } +``` + +Store is an object of bound methods to an underlying class instance. + +- `dispatch()` is the base dispatch function described above. +- `getState()` returns the current state of the store. +- `subscribe()` registers a function to be called on state changes. It returns an unsubscribe function. +- `getReducer()` and `replaceReducer()` are used to implement hot reloading, and should not be used directly. + +### Store-creating function + +```js +type CreateStore = (reducer: Function, initialState: any) => Store +``` + +A store-creating function is a function that creates a Redux store. Like with dispatching function, we must distinguish the base store-creating function, `createStore()`, from store-creating functions that are returned from higher-order stores. + +### Higher-order store + +```js +type HigherOrderStore = CreateStore +``` + +A higher-order store is a higher-order function that composes a store-creating function to return a new store-creating function. This is similar to middleware in that it allows you to alter the store interface in a composable way. From 1f19428bc077f598838d1b192233efa4b4ab7d42 Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Thu, 9 Jul 2015 00:26:05 -0700 Subject: [PATCH 17/33] Remove extraneous file --- docs/terminology.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 docs/terminology.md diff --git a/docs/terminology.md b/docs/terminology.md deleted file mode 100644 index e69de29bb2..0000000000 From 9de7445debda982cc4745ee93c8d6472d21c5804 Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Thu, 9 Jul 2015 00:32:23 -0700 Subject: [PATCH 18/33] Typos and clarficiation --- docs/types-and-terminology.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/types-and-terminology.md b/docs/types-and-terminology.md index 17cee0527c..17d26a18ea 100644 --- a/docs/types-and-terminology.md +++ b/docs/types-and-terminology.md @@ -1,7 +1,7 @@ Types and terminology ===================== -This is a glossary of the core terms in Redux, along with their type signatures. Types documented using [Flow notation](http://flowtype.org/docs/quick-reference.html#_). +This is a glossary of the core terms in Redux, along with their type signatures. Types are documented using [Flow notation](http://flowtype.org/docs/quick-reference.html#_). ### State @@ -57,12 +57,12 @@ An action creator is, quite simply, a function that creates an action. Do not co type IntermediateAction = any ``` -An *intermediate action* is a value that is sent to a dispatching function, but is not yet ready for consumption by the reducer; it will be transformed by middleware before being sent to the base `dispatch()` function. Intermediate actions are often asynchronous primitives, like a promise or a thunk, which are not dispatched themselves, but trigger dispatches once an operation has completed. +An **intermediate action** is a value that is sent to a dispatching function, but is not yet ready for consumption by the reducer; it will be transformed by middleware before being sent to the base `dispatch()` function. Intermediate actions are often asynchronous primitives, like a promise or a thunk, which are not dispatched themselves, but trigger dispatches once an operation has completed. ### Middleware ```js -type Middleware = ({ dispatch: Dispatch, getState: () => State }) => next: Dispatch => Dispatch +type Middleware = ({ dispatch: Dispatch, getState: () => State }) => (next: Dispatch) => Dispatch ``` A middleware is a higher-order function that composes a dispatch function to return a new dispatch function. @@ -79,7 +79,7 @@ Middleware is composable using function composition. type Store = { dispatch: Dispatch, getState: State, subscribe: Function, getReducer: Reducer, replaceReducer: void } ``` -Store is an object of bound methods to an underlying class instance. +A store is an object of bound methods to an underlying class instance. - `dispatch()` is the base dispatch function described above. - `getState()` returns the current state of the store. @@ -101,3 +101,7 @@ type HigherOrderStore = CreateStore ``` A higher-order store is a higher-order function that composes a store-creating function to return a new store-creating function. This is similar to middleware in that it allows you to alter the store interface in a composable way. + +Higher-order stores are much the same concept as higher-order components in React. + +Because a store is not an instance, but rather an plain-object collection of bound methods, copies can be easily created and modified without mutating the original store. From 7e5a829f1311f123bce79cdd23fef33af9f06aab Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Thu, 9 Jul 2015 00:37:19 -0700 Subject: [PATCH 19/33] Fix higher-order store type signature --- docs/types-and-terminology.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/types-and-terminology.md b/docs/types-and-terminology.md index 17d26a18ea..b032a5f5a2 100644 --- a/docs/types-and-terminology.md +++ b/docs/types-and-terminology.md @@ -97,7 +97,7 @@ A store-creating function is a function that creates a Redux store. Like with di ### Higher-order store ```js -type HigherOrderStore = CreateStore +type HigherOrderStore = (next: CreateStore) => CreateStore ``` A higher-order store is a higher-order function that composes a store-creating function to return a new store-creating function. This is similar to middleware in that it allows you to alter the store interface in a composable way. From 5433e2c68a693fb98edc2768ec01835dec35183e Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Thu, 9 Jul 2015 23:43:39 -0700 Subject: [PATCH 20/33] Fix Flow types --- docs/types-and-terminology.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/types-and-terminology.md b/docs/types-and-terminology.md index b032a5f5a2..8beb3d9475 100644 --- a/docs/types-and-terminology.md +++ b/docs/types-and-terminology.md @@ -34,7 +34,7 @@ We must distinguish between dispatching functions in general and the base dispat ### Reducer ```js -type Reducer = (state: S, action: A) => S +type Reducer = (state: S, action: A) => S ``` A **reducer** or **reducing function** is a function that accepts an accumulation and a value and returns a new accumulation. They are used to reduce a collection of values down to a single value. Reducers are not unique to Redux — they are a fundamental concept in functional programming. Even most non-functional languages, like JavaScript, have a built-in API for reducing. (In JavaScript, it's `Array.prototype.reduce()`.) @@ -46,7 +46,7 @@ Reducers are the most important concept in Redux. ### Action creator ```js -type ActionCreator = (...args) => Action | IntermediateAction +type ActionCreator = (...args: any) => Action | IntermediateAction ``` An action creator is, quite simply, a function that creates an action. Do not confuse the two terms — again, an action is a payload of information, and an action creator is a factory that creates them. @@ -62,7 +62,7 @@ An **intermediate action** is a value that is sent to a dispatching function, bu ### Middleware ```js -type Middleware = ({ dispatch: Dispatch, getState: () => State }) => (next: Dispatch) => Dispatch +type Middleware = (methods: { dispatch: Dispatch, getState: () => State }) => (next: Dispatch) => Dispatch; ``` A middleware is a higher-order function that composes a dispatch function to return a new dispatch function. From d64f1b4386d81dd97c251a0bd200358ecdd1adce Mon Sep 17 00:00:00 2001 From: ellbee Date: Sun, 12 Jul 2015 18:08:01 +0100 Subject: [PATCH 21/33] Update store.md --- docs/store.md | 126 ++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 108 insertions(+), 18 deletions(-) diff --git a/docs/store.md b/docs/store.md index 92d42a093d..638d672f5a 100644 --- a/docs/store.md +++ b/docs/store.md @@ -1,27 +1,117 @@ -### Initializing Redux +### The Redux Store -The simplest way to initialize a Redux instance is to give it an object whose values are your Store functions, and whose keys are their names. You may `import *` from the file with all your Store definitions to obtain such an object: +The store has the following responsibilities: + +* Holds application state +* Allows access to state via the `getState` method +* Allows state to be updated via the `dispatch` method +* Registers listeners via the `subscribe` method + +####Initialization + +The simplest way to initialize the store is to call `createStore` with an object of reducer functions. The following example sets up the store for an application that has both a `counter` reducer and a `todos` reducer. ```js -import { createRedux } from 'redux'; -import { Provider } from 'redux/react'; -import * as stores from '../stores/index'; +import { createStore } from 'redux'; +import { Provider } from 'react-redux'; +import counter from 'reducers/counter'; +import todos from 'reducers/todos'; -const redux = createRedux(stores); +const store = createStore({counter, todos}); ``` -Then pass `redux` as a prop to `` component in the root component of your app, and you're all set: +A recommended pattern is to create the object of reducer functions from a definition file. + +```js +// reducers/index.js +export { default as counter } from './counter' +export { default as todos } from './todos' +``` ```js -export default class App { - render() { - return ( - - {() => - - } - - ); - } -} +import { createStore } from 'redux'; +import { Provider } from 'react-redux'; +import * as reducers from './reducers/index'; + +const store = createStore(reducers); +``` + +You may optionally specify the initial state as the second argument to `createStore`. This is useful for hydrating the state of the client to match the state of a Redux application running on the server. + +```js +// server +const store = createStore(reducers); +store.dispatch(MyActionCreators.doSomething()); // fire action creators to fill the state +const state = store.getState(); // somehow pass this state to the client + +// client +const initialState = window.STATE_FROM_SERVER; +const store = createStore(reducers, initialState); +``` + +####Usage + +Store state is accessed using the `getState` method. Note that when you initialize the store by passing `createStore` an object of reducer functions, the name of each reducer becomes a top-level key on the state object. + +```js +store.getState(); +// { +// counter: 0, +// todos: [{ +// text: 'Use Redux', +// marked: false, +// id: 0 +// }]; +// } +``` + +Store state is updated by calling the `dispatch` method with an action understood by one or more reducers. + +```js +store.dispatch({ + type: INCREMENT_COUNTER +}); +store.dispatch({ + type: MARK_TODO, + id: 0 +}); +``` +```js +store.getState(); +// { +// counter: 1, +// todos: [{ +// text: 'Use Redux', +// marked: true, +// id: 0 +// }]; +// } +``` + +A listener can be registered with the store by passing a callback to the `subscribe` method. The `subscribe` method returns a function that can later be called to unsubscribe the listener. + +```js +let unsubscribe = store.subscribe(() => console.log('state change!')); +``` + +####Advanced Intitialization +`createStore` can be called with a single reducing function instead of an object of reducing functions. The `combineReducers` function can be used to compose multiple reducers. TODO: Real world use case? + +```js +import { createStore, combineReducers } from 'redux'; +import * as reducers from './reducers/index'; + +const combinedReducerFn = combineReducers(counter, todos); +const store = createStore(combinedReducerFn); +``` + +[Middleware](middleware.md) can be set up using `applyMiddleware`. + +```js +import { createStore, applyMiddleware } from 'redux' +import { logger, promise } from './middleware' +import * as reducers from './reducers/index'; + +const createWithMiddleware = applyMiddleware(logger, promise)(createStore); +const store = createWithMiddleware(reducers); ``` From 9dd724d4bf3e3268299463659c5af98e519eba28 Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Sun, 12 Jul 2015 13:44:21 -0700 Subject: [PATCH 22/33] Getting Started guide --- docs/getting-started.md | 271 ++++++++++++++++++++++++++++------ docs/types-and-terminology.md | 2 +- 2 files changed, 226 insertions(+), 47 deletions(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index 6f50873bf2..7c6fdd9a98 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -1,71 +1,250 @@ -### Actions +## Getting Started + +Don't be fooled by all the fancy talk about reducers, middleware, higher-order stores — Redux is incredibly simple. If you've ever built a Flux application, you will feel right at home. (If you're new to Flux, it's easy, too!) + +In this guide, we'll walk through the process of creating a simple Todo app. + +## Actions + +First, let's define some actions. + +**Actions** are payloads of information that send data from your application to your store. They are the *only* source of information for a store. You send them to the store using `store.dispatch()`. + +Here's an example action which represents adding a new todo item: ```js -// Still using constants... -import { INCREMENT_COUNTER, DECREMENT_COUNTER } from '../constants/ActionTypes'; +{ + type: ADD_TODO, + payload: { + text: 'Build my first Redux app' + } +} +``` -// But action creators are pure functions returning actions -export function increment() { - return { - type: INCREMENT_COUNTER - }; +Actions are plain JavaScript objects. By convention, actions should a have a `type` field that indicates the type of action being performed. Types should typically be defined as constants and imported from another module. + +```js +import { ADD_TODO, REMOVE_TODO } from '../actionTypes'; +``` + +Other than `type`, the structure of an action object is really up to you. If you're interested, check out [Flux Standard Action](https://github.com/acdlite/flux-standard-action) for recommendations on how actions should be constructed. + +We need one more action type, for removing todos: + +```js +{ + type: REMOVE_TODO, + payload: 123 } +``` -export function decrement() { - return { - type: DECREMENT_COUNTER +`payload` in this case indicates the id of the todo we want to remove. + +## Action creators + +**Action creators** exactly that — functions that create actions. It's easy to conflate the terms "action" and "action creator," so do your best to use the proper term. + +In *other* Flux implementations, action creators often trigger a dispatch when invoked, like so: + +```js +function addTodoWithDispatch(text) { + const action = { + type: ADD_TODO, + payload: { + text + } }; + dispatch(action); } +``` + +By contrast, in Redux action creators are **pure functions** with zero side-effects. They simply return an action: -// Can also be async if you return a function -export function incrementAsync() { - return dispatch => { - setTimeout(() => { - // Yay! Can invoke sync or async actions with `dispatch` - dispatch(increment()); - }, 1000); +```js +function addTodo(text) { + return { + type: ADD_TODO, + payload: { + text + } }; } +``` +This makes them more portable and easier to test. To actually initiate a dispatch, pass the result to the `dispatch()` function: -// Could also read state of a store in the callback form -export function incrementIfOdd() { - return (dispatch, getState) => { - const { counter } = getState(); +```js +dispatch(addTodo(text)); +dispatch(removeTodo(id)); +``` - if (counter % 2 === 0) { - return; - } +Or create **bound action creator** that automatically dispatches: - dispatch(increment()); - }; -} +```js +const boundAddTodo = text => dispatch(addTodo(text)); +const boundRemoveTodo = id => dispatch(addTodo(id)); ``` -### Stores +This is an example of a **higher-order function**. You'll notice this pattern a lot when working with Redux. Don't be scared, though — a higher-order function is just a function that returns another function. As you can see, ES6 arrow functions make working with higher-order functions a breeze. + +The `dispatch()` function can be accessed directly from the store as `store.dispatch()`, but more likely you'll access it using a helper like react-redux's `connect()`. + +## Reducers + +Now let's set up our store to respond to the action we defined above. + +Unlike other Flux implementations, Redux only has a single store, rather than a different store for each domain of data in your application. Instead of creating multiple stores that manually manage their own internal state, we create **reducers** that specify how to calculate state in response to new actions. + +A reducer looks like this: + ```js -// ... too, use constants -import { INCREMENT_COUNTER, DECREMENT_COUNTER } from '../constants/ActionTypes'; +(previousState, action) => newState +``` + +It's the type of function you would pass to `Array.prototype.reduce(reducer, ?initialValue)`. -// what's important is that Store is a pure function, -// and you can write it anyhow you like. +This may seem radical, but it turns out that this function signature is sufficient to express the entirety of the store model from traditional Flux — in a purely functional way. **This is a essence of Redux**. It's what enables all the cool features like hot reloading, time travel, and universal rendering. Aside from all that, though, it's simply a better model for expressing state changes. -// the Store signature is (state, action) => state, -// and the state shape is up to you: you can use primitives, -// objects, arrays, or even ImmutableJS objects. +[**See Dan's talk at React Europe for more on this topic**](https://www.youtube.com/watch?v=xsSnOQynTHs). + +Let's make a reducer for our Todo app: + +```js +const initialState = { todos: [], idCounter: 0 }; -export default function counter(state = 0, action) { - // this function returns the new state when an action comes +function todoReducer(state = initialState, action) { switch (action.type) { - case INCREMENT_COUNTER: - return state + 1; - case DECREMENT_COUNTER: - return state - 1; - default: - return state; + case ADD_TODO: + return { + ...state, + todos: [ + ...state.todos, + { text: action.payload, id: state.idCounter + 1 } + ], + idCounter: state.idCounter + 1 + }; + case REMOVE_TODO: + return { + ...state, + todos: state.todos.filter(todo => todo.id !== action.payload) + }; + default: + return state; } +} +``` + +Whoa, what's going on here? A few things to note: + +- `state` is the previous state of the store. Redux will dispatch a dummy action immediately upon creating your store, at which point `state` is undefined. From that point on, `state` is the previous value returned by the reducer. +- We're using a default parameter to specify the initial state of the store. +- We're using a switch statement to check the action type. +- **We're not mutating the previous state** — we're returning a **new** state object based on the **previous** state object. + +That last point is especially important: never mutate the previous state object. Always return a new state. Remember, reducers are pure functions, and should not perform mutations or side-effects. Here we're using the ES7 spread operator to shallow copy old state values to a new object. You can use a library like Immutable.js for a nicer API and better performance, since it uses [persistent data structures](http://en.wikipedia.org/wiki/Persistent_data_structure). Here's how that same store would look using immutable values: + +```js +const initialState = Immutable.Map({ todos: [], idCounter: 0 }); - // BUT THAT'S A SWITCH STATEMENT! - // Right. If you hate 'em, see the FAQ below. +function todoReducer(state = initialState, action) { + switch (action.type) { + case ADD_TODO: + return state + .update('todos', + todos => todos.push(Immutable.Map({ + text: action.payload, + id: state.get('idCounter') + }) + )) + .set('idCounter', state.get('idCounter') + 1); + case REMOVE_TODO: + return state + .update('todos', + todos => todos.filter(todo => todo.id !== action.payload ) + ); + default: + return state; + } } ``` + +If you're thinking "yuck, switch statements," remember that reducers are just functions — you can abstract away these details using helpers. Check out [redux-actions](https://github.com/acdlite/redux-actions) for an example of how to use higher-order functions to create reducers. + +## Creating a store + +Now we have some action creators and a reducer to handle them. The next step is to create our store. + +To create a store, pass a reducer to `createStore()`: + +```js +import { createStore } from 'redux'; +import todoReducer from '../reducers/todos'; +const store = createStore(todoReducer); +``` + +Usually you'll have multiple reducers for different domains of data in your app. You can use the `combineReducers()` helper to combine multiple reducers into one: + +```js +import { createStore, combineReducers } from 'redux'; +import * as reducers from '../reducers'; +const reducer = combine(reducers); +const store = createStore(reducer); +``` + +For example, if the object passed to `combineReducers()` looks like this: + +```js +const reducers = { + todos: todoReducer, + counter: counterReducer +}; +``` + +It will create a reducer which produces a state object like this: + +```js +const state = { + todos: todoState, + counter: counterState +}; +``` + +## Middleware + +Middleware is an optional feature of Redux that enables you to customize how dispatches are handled. Think of middleware as a certain type of plugin or extension for Redux. + +A common use for middleware is to enable asynchronous dispatches. For example, a promise middleware adds support for dispatching promises: + +```js +dispatch(Promise.resolve({ type: ADD_TODO, payload: { text: 'Use middleware!' } })); +``` +A promise middleware would detect that a promise was dispatched, intercept it, and instead dispatch with the resolved value at a future point in time. + +Middleware is very simple to create using function composition. We won't focus on how middleware works in this document but here's how you enable it when creating your store: + +```js +import { createStore, applyMiddleware } from 'redux'; +// where promise, thunk, and observable are examples of middleware +const store = applyMiddleware(promise, thunk, observable)(createStore)(reducer); +``` + +Yes, you read that correctly. [Read more about how middleware works here.](middleware.md) + +## Connecting to your views + +You'll rarely interact with the store object directly. Most often, you'll use some sort of binding to your preferred view library. + +Flux is most popular within the React community, but Redux works with any kind of view layer. The React bindings for Redux are available as react-redux — see that project for details on how to integrate with React. + +However, if you do find yourself needing to access the store directly, the API for doing so is very simple: + +- `store.dispatch()` dispatches an action. +- `store.getState()` gets the current state. +- `store.subscribe()` registers a listener which is called after every dispatch, and returns a function which you call to unsubscribe. + + +## Go make something great + +That's it! As you can see, despite the powerful features that Redux enables, the core of Redux is really quite simple. + +Please let us know if you have suggestions for how this guide could be improved. diff --git a/docs/types-and-terminology.md b/docs/types-and-terminology.md index 8beb3d9475..977f5e6228 100644 --- a/docs/types-and-terminology.md +++ b/docs/types-and-terminology.md @@ -76,7 +76,7 @@ Middleware is composable using function composition. ### Store ```js -type Store = { dispatch: Dispatch, getState: State, subscribe: Function, getReducer: Reducer, replaceReducer: void } +type Store = { dispatch: Dispatch, getState: () => State, subscribe: () => Function, getReducer: () => Reducer, replaceReducer: (reducer: Reducer) => void } ``` A store is an object of bound methods to an underlying class instance. From bfc8c26a0a890487494d8bed1ce3d350224f2a41 Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Sun, 12 Jul 2015 13:45:55 -0700 Subject: [PATCH 23/33] Fix page title --- docs/getting-started.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index 7c6fdd9a98..551710bc2a 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -1,4 +1,4 @@ -## Getting Started +# Getting Started Don't be fooled by all the fancy talk about reducers, middleware, higher-order stores — Redux is incredibly simple. If you've ever built a Flux application, you will feel right at home. (If you're new to Flux, it's easy, too!) From 126ac3ea7dcb4b26a919cec70c56b1e103c114fd Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Sun, 12 Jul 2015 13:53:03 -0700 Subject: [PATCH 24/33] Typo --- docs/getting-started.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index 551710bc2a..0de3f79c0c 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -103,7 +103,7 @@ A reducer looks like this: It's the type of function you would pass to `Array.prototype.reduce(reducer, ?initialValue)`. -This may seem radical, but it turns out that this function signature is sufficient to express the entirety of the store model from traditional Flux — in a purely functional way. **This is a essence of Redux**. It's what enables all the cool features like hot reloading, time travel, and universal rendering. Aside from all that, though, it's simply a better model for expressing state changes. +This may seem radical, but it turns out that this function signature is sufficient to express the entirety of the store model from traditional Flux — in a purely functional way. **This is the essence of Redux**. It's what enables all the cool features like hot reloading, time travel, and universal rendering. Aside from all that, though, it's simply a better model for expressing state changes. [**See Dan's talk at React Europe for more on this topic**](https://www.youtube.com/watch?v=xsSnOQynTHs). From 3ade4b02e3ecec7bcbcfc37799e08835b424bfce Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Sun, 12 Jul 2015 14:25:03 -0700 Subject: [PATCH 25/33] combine => combineReducers --- docs/getting-started.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index 0de3f79c0c..511210d3a3 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -187,7 +187,7 @@ Usually you'll have multiple reducers for different domains of data in your app. ```js import { createStore, combineReducers } from 'redux'; import * as reducers from '../reducers'; -const reducer = combine(reducers); +const reducer = combineReducers(reducers); const store = createStore(reducer); ``` From 6805327d4ceb5e00870e0584130aa1870e1e49b1 Mon Sep 17 00:00:00 2001 From: ellbee Date: Mon, 13 Jul 2015 07:47:39 +0100 Subject: [PATCH 26/33] Update store.md --- docs/store.md | 37 +++++++++++++++---------------------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/docs/store.md b/docs/store.md index 638d672f5a..7b46b71fb0 100644 --- a/docs/store.md +++ b/docs/store.md @@ -9,18 +9,19 @@ The store has the following responsibilities: ####Initialization -The simplest way to initialize the store is to call `createStore` with an object of reducer functions. The following example sets up the store for an application that has both a `counter` reducer and a `todos` reducer. +The simplest way to initialize the store is to call `createStore` with a reducer function. The following example has both a `counter` reducer and a `todos` reducer, so they need to be combined into a single reducer using the `combineReducers` function. ```js -import { createStore } from 'redux'; +import { createStore, combineReducers } from 'redux'; import { Provider } from 'react-redux'; import counter from 'reducers/counter'; import todos from 'reducers/todos'; -const store = createStore({counter, todos}); +const reducer = combineReducers({counter, todos}); +const store = createStore(reducer); ``` -A recommended pattern is to create the object of reducer functions from a definition file. +A recommended pattern is to create the object passed to `combineReducers` from a definition file. ```js // reducers/index.js @@ -29,10 +30,10 @@ export { default as todos } from './todos' ``` ```js -import { createStore } from 'redux'; -import { Provider } from 'react-redux'; +import { createStore, combineReducers } from 'redux'; import * as reducers from './reducers/index'; +const reducer = combineReducer(reducers); const store = createStore(reducers); ``` @@ -40,18 +41,18 @@ You may optionally specify the initial state as the second argument to `createSt ```js // server -const store = createStore(reducers); +const store = createStore(reducer); store.dispatch(MyActionCreators.doSomething()); // fire action creators to fill the state const state = store.getState(); // somehow pass this state to the client // client const initialState = window.STATE_FROM_SERVER; -const store = createStore(reducers, initialState); +const store = createStore(reducer, initialState); ``` ####Usage -Store state is accessed using the `getState` method. Note that when you initialize the store by passing `createStore` an object of reducer functions, the name of each reducer becomes a top-level key on the state object. +Store state is accessed using the `getState` method. Note that the name of each reducer in the object passed to `combineReducers` becomes a top-level key on the state object. ```js store.getState(); @@ -65,7 +66,7 @@ store.getState(); // } ``` -Store state is updated by calling the `dispatch` method with an action understood by one or more reducers. +Store state is updated by calling the `dispatch` method with an action understood by the reducer. ```js store.dispatch({ @@ -95,23 +96,15 @@ let unsubscribe = store.subscribe(() => console.log('state change!')); ``` ####Advanced Intitialization -`createStore` can be called with a single reducing function instead of an object of reducing functions. The `combineReducers` function can be used to compose multiple reducers. TODO: Real world use case? - -```js -import { createStore, combineReducers } from 'redux'; -import * as reducers from './reducers/index'; - -const combinedReducerFn = combineReducers(counter, todos); -const store = createStore(combinedReducerFn); -``` [Middleware](middleware.md) can be set up using `applyMiddleware`. ```js -import { createStore, applyMiddleware } from 'redux' +import { createStore, applyMiddleware, combineReducers } from 'redux' import { logger, promise } from './middleware' import * as reducers from './reducers/index'; -const createWithMiddleware = applyMiddleware(logger, promise)(createStore); -const store = createWithMiddleware(reducers); +const reducer = combineReducers(reducers); +const createStoreWithMiddleware = applyMiddleware(logger, promise)(createStore); +const store = createStoreWithMiddleware(reducer); ``` From f5a90cd8367286177c8c996f4a66dcd0e2a86fa6 Mon Sep 17 00:00:00 2001 From: ellbee Date: Mon, 13 Jul 2015 07:53:41 +0100 Subject: [PATCH 27/33] Update store.md --- docs/store.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/store.md b/docs/store.md index 7b46b71fb0..06a29ceaa3 100644 --- a/docs/store.md +++ b/docs/store.md @@ -33,8 +33,8 @@ export { default as todos } from './todos' import { createStore, combineReducers } from 'redux'; import * as reducers from './reducers/index'; -const reducer = combineReducer(reducers); -const store = createStore(reducers); +const reducer = combineReducers(reducers); +const store = createStore(reducer); ``` You may optionally specify the initial state as the second argument to `createStore`. This is useful for hydrating the state of the client to match the state of a Redux application running on the server. From 9050a5f92f5912aff270577ded395574dbbc0f7c Mon Sep 17 00:00:00 2001 From: Martin Camacho Date: Mon, 13 Jul 2015 14:39:23 -0400 Subject: [PATCH 28/33] Reference react-redux within react.md --- docs/react.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/react.md b/docs/react.md index 600cefd850..6d7f91effc 100644 --- a/docs/react.md +++ b/docs/react.md @@ -1,3 +1,5 @@ +React bindings reside in the [react-redux](https://github.com/gaearon/react-redux) repository. + ### Components #### Dumb Components @@ -36,7 +38,7 @@ export default class Counter { import React from 'react'; import { bindActionCreators } from 'redux'; -import { Connector } from 'redux/react'; +import { Connector } from 'react-redux'; import Counter from '../components/Counter'; import * as CounterActions from '../actions/CounterActions'; @@ -69,7 +71,7 @@ The `@connect` decorator lets you create smart components less verbosely: ```js import React from 'react'; import { bindActionCreators } from 'redux'; -import { connect } from 'redux/react'; +import { connect } from 'react-redux'; import Counter from '../components/Counter'; import * as CounterActions from '../actions/CounterActions'; @@ -91,9 +93,9 @@ export default class CounterApp { ### React Native -To use Redux with React Native, just replace imports from `redux/react` with `redux/react-native`: +To use Redux with React Native, just replace imports from `react-redux` with `react-redux/native`: ```js import { bindActionCreators } from 'redux'; -import { Provider, Connector } from 'redux/react-native'; +import { Provider, Connector } from 'react-redux/native'; ``` From 8d78e49d1373a01d97e02df29b13f2a9789c2613 Mon Sep 17 00:00:00 2001 From: ellbee Date: Mon, 13 Jul 2015 21:15:01 +0100 Subject: [PATCH 29/33] Update store.md --- docs/store.md | 51 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/docs/store.md b/docs/store.md index 06a29ceaa3..53b39a471f 100644 --- a/docs/store.md +++ b/docs/store.md @@ -3,25 +3,58 @@ The store has the following responsibilities: * Holds application state -* Allows access to state via the `getState` method -* Allows state to be updated via the `dispatch` method -* Registers listeners via the `subscribe` method +* Allows access to state via `getState` +* Allows state to be updated via `dispatch` +* Registers listeners via `subscribe` ####Initialization -The simplest way to initialize the store is to call `createStore` with a reducer function. The following example has both a `counter` reducer and a `todos` reducer, so they need to be combined into a single reducer using the `combineReducers` function. +To intialize a store you simply call `createStore` with a reducer. + +```js +import { createStore } from 'redux'; +import counter from './reducers/counter'; + +const store = createStore(counter); +``` + +A redux store works with a single reducer, but in the following example we would like to use the functionality of both the `counter` and `todos` reducers. To do this we need to somehow combine `counter` and `todos` into a single reducer. Here is one approach: + +```js +import { createStore } from 'redux'; +import counter from './reducers/counter'; +import todos from './reducers/todos'; + +// set up the initial combined state +const initialState = { + counterState: undefined, + todoState: undefined +}; + +function combinedReducer(state = initialState, action) { + // call each reducer separately + const counterState = counter(state.counterState, action); + const todoState = todos(state.todoState, action); + + // combine updated state created by each reducer into the new combined state + return { counterState, todoState }; +} + +const store = createStore(combinedReducer); +``` + +As combining reducers is so common there is a helper function named `combineReducers` to assist. `combineReducers` takes an object of reducers and returns them combined into a single reducer. ```js import { createStore, combineReducers } from 'redux'; -import { Provider } from 'react-redux'; -import counter from 'reducers/counter'; -import todos from 'reducers/todos'; +import counter from './reducers/counter'; +import todos from './reducers/todos'; -const reducer = combineReducers({counter, todos}); +const reducer = combineReducers({ counter, todos }); const store = createStore(reducer); ``` -A recommended pattern is to create the object passed to `combineReducers` from a definition file. +A recommended pattern is to import the object passed to `combineReducers` from a definition file. ```js // reducers/index.js From 1d207ba6f2ba287d466c6ec874cf3a1c4857a86f Mon Sep 17 00:00:00 2001 From: ellbee Date: Mon, 13 Jul 2015 21:19:58 +0100 Subject: [PATCH 30/33] Update store.md --- docs/store.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/store.md b/docs/store.md index 53b39a471f..0016a3a18c 100644 --- a/docs/store.md +++ b/docs/store.md @@ -9,7 +9,7 @@ The store has the following responsibilities: ####Initialization -To intialize a store you simply call `createStore` with a reducer. +To intialize a store you simply call `createStore` with a reducer: ```js import { createStore } from 'redux'; @@ -18,7 +18,7 @@ import counter from './reducers/counter'; const store = createStore(counter); ``` -A redux store works with a single reducer, but in the following example we would like to use the functionality of both the `counter` and `todos` reducers. To do this we need to somehow combine `counter` and `todos` into a single reducer. Here is one approach: +`createStore` intializes the store with a single reducer, but in the following example we would like to use functionality from both the `counter` and `todos` reducers. To do this we need to somehow combine `counter` and `todos` into a single reducer. Here is one approach: ```js import { createStore } from 'redux'; @@ -43,7 +43,7 @@ function combinedReducer(state = initialState, action) { const store = createStore(combinedReducer); ``` -As combining reducers is so common there is a helper function named `combineReducers` to assist. `combineReducers` takes an object of reducers and returns them combined into a single reducer. +As combining reducers is so common there is a helper function named `combineReducers` to assist. `combineReducers` takes an object of reducers and returns them combined into a single reducer. Here is the previous example using `combineReducers`: ```js import { createStore, combineReducers } from 'redux'; @@ -54,7 +54,7 @@ const reducer = combineReducers({ counter, todos }); const store = createStore(reducer); ``` -A recommended pattern is to import the object passed to `combineReducers` from a definition file. +A recommended pattern is to import the object passed to `combineReducers` from a definition file: ```js // reducers/index.js From 2bb3769d3886470c6452914060a8139c32d39d8a Mon Sep 17 00:00:00 2001 From: ellbee Date: Tue, 14 Jul 2015 22:01:45 +0100 Subject: [PATCH 31/33] Update store.md --- docs/store.md | 46 ++++++++++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/docs/store.md b/docs/store.md index 0016a3a18c..9e1fec5a1a 100644 --- a/docs/store.md +++ b/docs/store.md @@ -13,17 +13,17 @@ To intialize a store you simply call `createStore` with a reducer: ```js import { createStore } from 'redux'; -import counter from './reducers/counter'; +import counterReducer from './reducers/counter'; -const store = createStore(counter); +const store = createStore(counterReducer); ``` -`createStore` intializes the store with a single reducer, but in the following example we would like to use functionality from both the `counter` and `todos` reducers. To do this we need to somehow combine `counter` and `todos` into a single reducer. Here is one approach: +`createStore` intializes the store with a single reducer, but in the following example we would like to use functionality from both the `counter` and `todos` reducers. To do this we need to somehow combine `counter` and `todos` into a single reducer. Here is one approach: ```js import { createStore } from 'redux'; -import counter from './reducers/counter'; -import todos from './reducers/todos'; +import counterReducer from './reducers/counter'; +import todosReducer from './reducers/todos'; // set up the initial combined state const initialState = { @@ -31,30 +31,44 @@ const initialState = { todoState: undefined }; -function combinedReducer(state = initialState, action) { +function masterReducer(state = initialState, action) { // call each reducer separately - const counterState = counter(state.counterState, action); - const todoState = todos(state.todoState, action); + const counterState = counterReducer(state.counterState, action); + const todoState = todosReducer(state.todoState, action); // combine updated state created by each reducer into the new combined state return { counterState, todoState }; } -const store = createStore(combinedReducer); +const store = createStore(masterReducer); ``` -As combining reducers is so common there is a helper function named `combineReducers` to assist. `combineReducers` takes an object of reducers and returns them combined into a single reducer. Here is the previous example using `combineReducers`: +Combining reducers is very common so there is a helper function named `combineReducers` to assist. `combineReducers` takes an object of reducers and combines them into a single reducer. Here is the previous example using `combineReducers`: ```js import { createStore, combineReducers } from 'redux'; -import counter from './reducers/counter'; -import todos from './reducers/todos'; +import counterReducer from './reducers/counter'; +import todosReducer from './reducers/todos'; -const reducer = combineReducers({ counter, todos }); -const store = createStore(reducer); +const reducers = { + counter: counterReducer, + todos: todosReducer +} + +const masterReducer = combineReducers(reducers); +const store = createStore(masterReducer); +``` + +Note that the key of each reducer in the reducer object passed to `combineReducers` becomes a top-level key on the state object returned by the combined reducer. In the previous example, the state object returned by `masterReducer` looks like this: + +```js +const state = { + counter: counterState, + todos: todosState +}; ``` -A recommended pattern is to import the object passed to `combineReducers` from a definition file: +A recommended pattern is to use `import *` to import an object of reducers from a definition file: ```js // reducers/index.js @@ -85,7 +99,7 @@ const store = createStore(reducer, initialState); ####Usage -Store state is accessed using the `getState` method. Note that the name of each reducer in the object passed to `combineReducers` becomes a top-level key on the state object. +Store state is accessed using the `getState` method. ```js store.getState(); From a6f16ee9534fedde09f3ba836b877ad0a0b07e8e Mon Sep 17 00:00:00 2001 From: "C. T. Lin" Date: Wed, 15 Jul 2015 12:43:23 +0800 Subject: [PATCH 32/33] Remove extra 'a' in doc --- docs/getting-started.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index 511210d3a3..62de027c34 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -21,7 +21,7 @@ Here's an example action which represents adding a new todo item: } ``` -Actions are plain JavaScript objects. By convention, actions should a have a `type` field that indicates the type of action being performed. Types should typically be defined as constants and imported from another module. +Actions are plain JavaScript objects. By convention, actions should have a `type` field that indicates the type of action being performed. Types should typically be defined as constants and imported from another module. ```js import { ADD_TODO, REMOVE_TODO } from '../actionTypes'; From 1452d8bc70628840a75436d8b8ca7805ae167477 Mon Sep 17 00:00:00 2001 From: "C. T. Lin" Date: Fri, 17 Jul 2015 12:16:46 +0800 Subject: [PATCH 33/33] Fix a broken link in middleware.md --- docs/middleware.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/middleware.md b/docs/middleware.md index 93acf73a8b..5db9d4d0b9 100644 --- a/docs/middleware.md +++ b/docs/middleware.md @@ -16,7 +16,7 @@ To enable middleware in your Redux app, use `applyMiddleware()`. ### `applyMiddleware(...middlewares)` -This function returns a [higher-order store](higher-order store). You don't need to worry about that if you're not interested — here's how you use it: +This function returns a [higher-order store](higher-order-stores.md). You don't need to worry about that if you're not interested — here's how you use it: ```js const store = applyMiddleware(thunk, promise, observable)(createStore)(reducer);