From 34e636b3edc2cb6bcc357d0ae16fc9c3173e5028 Mon Sep 17 00:00:00 2001 From: Wei Gao Date: Mon, 8 Oct 2018 02:01:47 +0800 Subject: [PATCH 1/5] Fix typo and remove unnecessary comments from `GettingStarted.md` --- docs/GettingStarted.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/docs/GettingStarted.md b/docs/GettingStarted.md index 68126f0c8..297efa0f4 100644 --- a/docs/GettingStarted.md +++ b/docs/GettingStarted.md @@ -308,12 +308,8 @@ export const getTodoList = store => getTodosState(store) ? getTodosState(store).allIds : []; export const getTodoById = (store, id) => - getTodoState(store) ? { ...getTodosState(store).byIds[id], id } : {}; + getTodosState(store) ? { ...getTodosState(store).byIds[id], id } : {}; -/** - * example of a slightly more complex selector - * select from store combining information from multiple reducers - */ export const getTodos = store => getTodoList(store).map(id => getTodoById(store, id)); ``` @@ -462,7 +458,6 @@ Meanwhile, we also need to update our `` component to filter todos a // redux/selectors.js // ... other selectors - export const getTodosByVisibilityFilter = (store, visibilityFilter) => { const allTodos = getTodos(store); switch (visibilityFilter) { From f05b1ad3ee49b677e51502b79d5390c1fbb46f3c Mon Sep 17 00:00:00 2001 From: Wei Gao Date: Mon, 8 Oct 2018 02:06:10 +0800 Subject: [PATCH 2/5] Rename getting started to kebab --- docs/README.md | 4 +--- docs/{GettingStarted.md => getting-started.md} | 0 2 files changed, 1 insertion(+), 3 deletions(-) rename docs/{GettingStarted.md => getting-started.md} (100%) diff --git a/docs/README.md b/docs/README.md index c4258c954..4ca9d856c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,8 +1,6 @@ # Table of Contents - - -- [Getting Started: adding React-Redux to a React todo app](./GettingStarted.md) +- [Getting Started: adding React-Redux to a React todo app](./getting-started.md) - [API](api.md#api) - [``](api.md#provider-store) - [`connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])`](api.md#connectmapstatetoprops-mapdispatchtoprops-mergeprops-options) diff --git a/docs/GettingStarted.md b/docs/getting-started.md similarity index 100% rename from docs/GettingStarted.md rename to docs/getting-started.md From 1d9069a8296f4d0891ddc49029cdd671fcca044a Mon Sep 17 00:00:00 2001 From: Wei Gao Date: Mon, 8 Oct 2018 02:48:04 +0800 Subject: [PATCH 3/5] Add mapState piece --- docs/README.md | 2 + ...ct-extracting-data-with-mapStateToProps.md | 232 ++++++++++++++++++ docs/getting-started.md | 4 +- website/sidebars.json | 1 + 4 files changed, 237 insertions(+), 2 deletions(-) create mode 100644 docs/connect-extracting-data-with-mapStateToProps.md diff --git a/docs/README.md b/docs/README.md index 4ca9d856c..8ccb64c27 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,8 @@ # Table of Contents - [Getting Started: adding React-Redux to a React todo app](./getting-started.md) +- Using React-Redux + - [Connect: Extracting Data with `mapStateToProps`](./connect-extracting-data-with-mapStateToProps.md) - [API](api.md#api) - [``](api.md#provider-store) - [`connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])`](api.md#connectmapstatetoprops-mapdispatchtoprops-mergeprops-options) diff --git a/docs/connect-extracting-data-with-mapStateToProps.md b/docs/connect-extracting-data-with-mapStateToProps.md new file mode 100644 index 000000000..1e3cbd043 --- /dev/null +++ b/docs/connect-extracting-data-with-mapStateToProps.md @@ -0,0 +1,232 @@ +--- +id: connect-extracting-data-with-mapStateToProps +title: Connect: Extracting Data with `mapStateToProps` +hide_title: true +sidebar_label: Connect: Extracting Data with `mapStateToProps` +--- + +# Connect: Extracting Data with `mapStateToProps` +As the first argument passed in to `connect`, `mapStateToProps` is used for selecting the part of the data from the store that the connected component needs. It’s frequently referred to as just `mapState` for short. + +- It is called every time the store state changes. +- It receives the entire store state, and should return an object of data this component needs. + + +## Defining `mapStateToProps` + +`mapStateToProps` should be defined as a function: + +```jsx +function mapStateToProps(state[, ownProps]) +``` + +It should take `state`, optionally `ownProps`, and return a plain object containing the data that the connected component needs. + +### Arguments + +- `state` + +The `mapStateToProps` function should always be written with at least `state` passed in. It will be called every time when the store changes. If you do not wish to subscribe to the store, pass `null` or `undefined` to `connect` in place of `mapStateToProps`. + +```jsx +// TodoList.js +// ... + +const mapStateToProps = state => { + const { todos } = state; + return { todoList: todos.allIds }; +}; + +export default connect(mapStateToProps)(TodoList); +``` + +- `ownProps` (optional) + +You may define the function with a second argument, `ownProps`, if your component needs the data from its own props to retrieve data from the store. + +```jsx +// Todo.js + +const mapStateToProps = (state, ownProps) => { + const { visibilityFilter } = state; + const { id } = ownProps; + const todo = getTodoById(state, id); + + // component receives additionally: + return { todo, visibilityFilter }; +}; +``` + +**The arity of `mapStateToProps` affects its behavior:** + +With just `state`, the function runs whenever the root store state object is different. With `state, ownProps`, it runs any time the store state is different AND when the wrapper props have changed. + +**Mandatory number of arguments determines whether `mapStateToProps` will receive `ownProps`:** + +If the formal definition of the function contains one mandatory parameter, `mapStateToProps` will _not_ receive `ownProps`: + +```jsx +function mapStateToProps(state) { + console.log(state); // state + console.log(arguments[1]); // undefined +} +const mapStateToProps = (state, ownProps = {}) => { + console.log(state); // state + console.log(ownProps); // undefined +} +``` + +It _will_ receive `ownProps` when the formal definition of the function contains zero or two mandatory parameters: + +```jsx +const mapStateToProps = (state, ownProps) => { + console.log(state); // state + console.log(ownProps); // ownProps +} + +function mapStateToProps() { + console.log(arguments[0]); // state + console.log(arguments[1]); // ownProps +} + +const mapStateToProps = (...args) => { + console.log(args[0]); // state + console.log(args[1]); // ownProps +} +``` + +### Return +In the most common usages, you should let your `mapStateToProps` return a plain object that contains the data the component needs: + +- it will be merged into the component’s props +- it will be used to decide whether the component will re-render + + +> Note: In advanced scenarios where you need more control over the rendering performance, `mapStateToProps` can also return a function. In this case, that function will be used as `mapStateToProps` for a particular component instance. This allows you to do per-instance memoization. You can refer to [#279](https://github.com/reduxjs/react-redux/pull/279) and the tests it adds for more details. Most apps never need this. + +React-Redux internally implements the `shouldComponentUpdate` method such that the wrapper component re-renders precisely when the data it needs change. By default, React-Redux decides whether the contents of the object returned from `mapStateToProps` are different using `===` comparison on each fields of the returned object. Returning a mutated object of the same reference is a common mistake resulting in component not re-rendering when expected. + +However, if `mapStateToProps` is written in such way that it returns a different copy of the data each time, the component may re-render too many times: + +```jsx +const mapStateToProps = state => { + // re-renders even if the list may remain the same + return { + completedTodos: state.todos.filter(todo => todo.completed) + }; +} +``` + +To summarize the behavior of the component wrapped by `connect` with `mapStateToProps` to extract data from the store: + +| | `(state) => stateProps` | `(state, ownProps) => stateProps` | +| ---------------------------- | -------------------------------------- | -------------------------------------------------------------------------------------------- | +| `mapStateToProps` runs when: | store `state` is `===` different | store `state` changes
or
any field of `ownProps` is different | +| component re-renders when: | any field of `stateProps` is different | any field of `stateProps` is different
or
any field of `ownProps` is different | + +New CodeSandbox [here](https://codesandbox.io/s/yvvqxj4p11): + +- `TodoList` now connects to `todos.allIds` +- `Todo` connects to `todos.byIds` with `id` from `ownProps`, and gets `visibilityFilter` +- `Todo` contains a simpler filter in itself to determine whether to show or hide each todo +- a bunch of commented out console logs to play around and see how the component behaves + + +## Keys to Good Redux Performance + +### `mapStateToProps` Functions Should Be Fast + +Whenever the store changes, all of the `mapStateToProps` functions of all of the connected components will run. That is a lot of callings of `mapStateToProps` at a very high frequency. `mapStateToProps` should be fast. + +Avoid doing very expensive work in `mapStateToProps` unless absolutely necessary. This includes: + +**Complex filtering and transformations** + +You will probably need to do complex transformations at some point. Consider first whether the transformation fits better in another place: + +- Reducer is a potentially better candidate, because it concerns its own related data. +- Component’s lifecycle events such as `componentDidUpdate` or `render`, because their number of calls are cut down by `mapStateToProps` functions. +- If `mapStateToProps` is unavoidably the most suitable place for some costly computations, we suggest using memoized selectors. You may refer to the links at the end of this section for more information. + +**Expensive functions such as `toJS()` of `Immutable.js`** + +[Immutable.js author Lee Byron on Twitter](https://twitter.com/leeb/status/746733697093668864?lang=en) explicitly advises avoiding `toJS` when performance is a concern: + +> Perf tip for #immutablejs: avoid .toJS() .toObject() and .toArray() all slow full-copy operations which render structural sharing useless. + +There's several other performance concerns to take into consideration with Immutable.js - see the list of links at the end of this post for more information. + +**Await async function calls** + +A `mapStateToProps` function should be pure and 100% synchronous, like a reducer — state (+ownProps) in, resulting props out. +No AJAX calls, no async stuff, no dispatching actions. + +### Let `mapStateToProps` Reshape the Data from the Store + +`mapStateToProps` functions can, and should, do a lot more than just `return state.someSlice`. +They have the responsibility of "re-shaping" store data as needed for that component. +However, such computations should subject to its own performance requirement mentioned above. + +### Do Not Return New References of Objects + +To avoid unnecessary re-rendering, do not return new references of objects. + +Whenever the store state changes, all connected components will receive the updated store state and run `mapStateToProps` functions with them. +Odds are the changes happen only to a small slice of the store, and unrelated components should just receive the same data. + +React-Redux does those shallow comparisons to avoid unnecessary re-rendering. +However, it’s easy to accidentally return new object or array references every time, which would cause your component to re-render even if the data is actually the same. + +Some examples are: + +- Mapping over arrays `array.map` +- Merging arrays with `array.concat` +- Copying values with `Object.assign` +- Copying values with the spread operator `{ ...oldState, ...newData }` + +In `mapState` functions, it suffices to use the dot accessor for object values + +```jsx +const mapStateToProps = state => { + // destructuring is ok + const { todos } = state; + return { + // do this: + user: state.user, + todos, + } +} +``` + +### Connect More Components + +Overall performance is a balance between the overhead of more `mapStateToProps` calls, and time spent by React re-rendering. +Redux subscriptions are O(n) - every additional subscriber means a bit more work every time an action is dispatched. +Fortunately, benchmarks have shown that the cost of more connected components is generally less than the cost of more wasted re-rendering. +See this FAQ ["Should I only connect my top component, or can I connect multiple components in my tree?"](https://redux.js.org/faq/reactredux#react-multiple-components) for more discussions. + + + +## Links and References + +**Tutorials** + +- [Practical Redux Series, Part 6: Connected Lists, Forms, and Performance](https://blog.isquaredsoftware.com/2017/01/practical-redux-part-6-connected-lists-forms-and-performance/) + +**Performance** + +- [Lee Byron's Tweet Suggesting to avoid `toJS`, `toArray` and `toObject` for Performance](https://twitter.com/leeb/status/746733697093668864) +- [Improving React and Redux performance with Reselect](https://blog.rangle.io/react-and-redux-performance-with-reselect/) +- [Relevant links to immutable data](https://github.com/markerikson/react-redux-links/blob/master/react-performance.md#immutable-data) + +**Q&A** + +- [Why Is My Component Re-Rendering Too Often?](https://redux.js.org/faq/reactredux#why-is-my-component-re-rendering-too-often) +- [Why isn't my component re-rendering, or my mapStateToProps running](https://redux.js.org/faq/reactredux#why-isnt-my-component-re-rendering-or-my-mapstatetoprops-running) +- [How can I speed up my mapStateToProps?](https://redux.js.org/faq/reactredux#why-is-my-component-re-rendering-too-often) +- [Should I only connect my top component, or can I connect multiple components in my tree?](https://redux.js.org/faq/reactredux#why-is-my-component-re-rendering-too-often) \ No newline at end of file diff --git a/docs/getting-started.md b/docs/getting-started.md index 297efa0f4..f56c2e08c 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -1,8 +1,8 @@ --- id: getting-started -title: Getting started +title: Getting Started hide_title: true -sidebar_label: Getting started +sidebar_label: Getting Started --- # Getting Started diff --git a/website/sidebars.json b/website/sidebars.json index 371ac498e..af3f2346f 100644 --- a/website/sidebars.json +++ b/website/sidebars.json @@ -1,6 +1,7 @@ { "docs": { "Introduction": ["getting-started"], + "Using React Redux": ["connect-extracting-data-with-mapStateToProps"], "API Reference": ["api", "api/provider"], "Guides": ["troubleshooting"] } From 979768bf111729028bd6ed08ee69d2cc05c105d2 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sun, 7 Oct 2018 16:57:55 -0400 Subject: [PATCH 4/5] Restructure the mapState page content --- ...ct-extracting-data-with-mapStateToProps.md | 230 ++++++++++-------- 1 file changed, 126 insertions(+), 104 deletions(-) diff --git a/docs/connect-extracting-data-with-mapStateToProps.md b/docs/connect-extracting-data-with-mapStateToProps.md index 1e3cbd043..86a2666fd 100644 --- a/docs/connect-extracting-data-with-mapStateToProps.md +++ b/docs/connect-extracting-data-with-mapStateToProps.md @@ -16,23 +16,28 @@ As the first argument passed in to `connect`, `mapStateToProps` is used for sele `mapStateToProps` should be defined as a function: -```jsx -function mapStateToProps(state[, ownProps]) +```js +function mapStateToProps(state, ownProps?) ``` -It should take `state`, optionally `ownProps`, and return a plain object containing the data that the connected component needs. +It should take a first argument called `state`, optionally a second argument called `ownProps`, and return a plain object containing the data that the connected component needs. + +This function should be passed as the first argument to `connect`, and will be will be called every time when the Redux store state changes. If you do not wish to subscribe to the store, pass `null` or `undefined` to `connect` in place of `mapStateToProps`. + +**It does not matter if a `mapStateToProps` function is written using the `function` keyword (`function mapState(state) { }` ) or as an arrow function (`const mapState = (state) => { }` )** - it will work the same either way. ### Arguments -- `state` +#### `state` + +The first argument to a `mapStateToProps` function is the entire Redux store state (the same value returned by a call to `store.getState()`). Because of this, the first argument is traditionally just called `state`. (While you can give the argument any name you want, calling it `store` would be incorrect - it's the "state value", not the "store instance".) -The `mapStateToProps` function should always be written with at least `state` passed in. It will be called every time when the store changes. If you do not wish to subscribe to the store, pass `null` or `undefined` to `connect` in place of `mapStateToProps`. +The `mapStateToProps` function should always be written with at least `state` passed in. -```jsx +```js // TodoList.js -// ... -const mapStateToProps = state => { +function mapStateToProps(state) { const { todos } = state; return { todoList: todos.allIds }; }; @@ -40,14 +45,14 @@ const mapStateToProps = state => { export default connect(mapStateToProps)(TodoList); ``` -- `ownProps` (optional) +#### `ownProps` (optional) -You may define the function with a second argument, `ownProps`, if your component needs the data from its own props to retrieve data from the store. +You may define the function with a second argument, `ownProps`, if your component needs the data from its own props to retrieve data from the store. This argument will contain all of the props given to the wrapper component that was generated by `connect`. -```jsx +```js // Todo.js -const mapStateToProps = (state, ownProps) => { +function mapStateToProps(state, ownProps) { const { visibilityFilter } = state; const { id } = ownProps; const todo = getTodoById(state, id); @@ -55,67 +60,72 @@ const mapStateToProps = (state, ownProps) => { // component receives additionally: return { todo, visibilityFilter }; }; + +// Later, in your application, a parent component renders: + +// and your component receives props.id, props.todo, and props.visibilityFilter ``` -**The arity of `mapStateToProps` affects its behavior:** +You do not need to include values from `ownProps` in the object returned from `mapStateToProps`. `connect` will automatically merge those different prop sources into a final set of props. + +### Return -With just `state`, the function runs whenever the root store state object is different. With `state, ownProps`, it runs any time the store state is different AND when the wrapper props have changed. +Your `mapStateToProps` function should return a plain object that contains the data the component needs: -**Mandatory number of arguments determines whether `mapStateToProps` will receive `ownProps`:** +- Each field in the object will become a prop for your actual component +- The values in the fields will be used to determine if your component needs to re-render -If the formal definition of the function contains one mandatory parameter, `mapStateToProps` will _not_ receive `ownProps`: +For example: -```jsx +```js function mapStateToProps(state) { - console.log(state); // state - console.log(arguments[1]); // undefined -} -const mapStateToProps = (state, ownProps = {}) => { - console.log(state); // state - console.log(ownProps); // undefined + return { + a : 42, + todos : state.todos, + filter : state.visibilityFilter + } } + +// component will receive: props.a, props.todos, and props.filter ``` -It _will_ receive `ownProps` when the formal definition of the function contains zero or two mandatory parameters: -```jsx -const mapStateToProps = (state, ownProps) => { - console.log(state); // state - console.log(ownProps); // ownProps -} +> Note: In advanced scenarios where you need more control over the rendering performance, `mapStateToProps` can also return a function. In this case, that function will be used as the final `mapStateToProps` for a particular component instance. This allows you to do per-instance memoization. See the [Advanced Usage]() section of the docs for more details, as well as [PR #279](https://github.com/reduxjs/react-redux/pull/279) and the tests it adds. Most apps never need this. -function mapStateToProps() { - console.log(arguments[0]); // state - console.log(arguments[1]); // ownProps -} -const mapStateToProps = (...args) => { - console.log(args[0]); // state - console.log(args[1]); // ownProps -} -``` +## Usage Guidelines -### Return -In the most common usages, you should let your `mapStateToProps` return a plain object that contains the data the component needs: -- it will be merged into the component’s props -- it will be used to decide whether the component will re-render +### Let `mapStateToProps` Reshape the Data from the Store +`mapStateToProps` functions can, and should, do a lot more than just `return state.someSlice`. **They have the responsibility of "re-shaping" store data as needed for that component.** This may include returning a value as a specific prop name, combining pieces of data from different parts of the state tree, and transforming the store data in different ways. -> Note: In advanced scenarios where you need more control over the rendering performance, `mapStateToProps` can also return a function. In this case, that function will be used as `mapStateToProps` for a particular component instance. This allows you to do per-instance memoization. You can refer to [#279](https://github.com/reduxjs/react-redux/pull/279) and the tests it adds for more details. Most apps never need this. -React-Redux internally implements the `shouldComponentUpdate` method such that the wrapper component re-renders precisely when the data it needs change. By default, React-Redux decides whether the contents of the object returned from `mapStateToProps` are different using `===` comparison on each fields of the returned object. Returning a mutated object of the same reference is a common mistake resulting in component not re-rendering when expected. +### Use Selector Functions to Extract and Transform Data -However, if `mapStateToProps` is written in such way that it returns a different copy of the data each time, the component may re-render too many times: +We highly encourage the use of "selector" functions to help encapsulate the process of extracting values from specific locations in the state tree. Memoized selector functions also play a key role in improving application performance (see the following sections in this page and the [Advanced Usage: Performance]() page for more details on why and how to use selectors.) + + +### `mapStateToProps` Functions Should Be Fast + +Whenever the store changes, all of the `mapStateToProps` functions of all of the connected components will run. Because of this, your `mapStateToProps` functions should run as fast as possible. This also means that a slow `mapStateToProps` function can be a potential bottleneck for your application. + +As part of the "re-shaping data" idea, `mapStateToProps` functions frequently need to transform data in various ways (such as filtering an array, mapping an array of IDs to their corresponding objects, or extracting plain JS values from Immutable.js objects). These transformations can often be expensive, both in terms of cost to execute the transformation, and whether the component re-renders as a result. If performance is a concern, ensure that these transformations are only run if the input values have changed. + + +### `mapStateToProps` Functions Should Be Pure and Synchronous + +Much like a Redux reducer, a `mapStateToProps` function should always be 100% pure and synchronous. It should simply take `state` (and `ownProps`) as arguments, and return the data the component needs as props. It should _not_ be used to trigger asynchronous behavior like AJAX calls for data fetching, and the functions should not be declared as `async`. + + + +## `mapStateToProps` and Performance + + +### Return Values Determine If Your Component Re-Renders + +React-Redux internally implements the `shouldComponentUpdate` method such that the wrapper component re-renders precisely when the data your component needs has changed. By default, React-Redux decides whether the contents of the object returned from `mapStateToProps` are different using `===` comparison (a "shallow equality" check) on each fields of the returned object. If any of the fields have changed, then your component will be re-rendered so it can receive the updated values as props. Note that returning a mutated object of the same reference is a common mistake that can result in your component not re-rendering when expected. -```jsx -const mapStateToProps = state => { - // re-renders even if the list may remain the same - return { - completedTodos: state.todos.filter(todo => todo.completed) - }; -} -``` To summarize the behavior of the component wrapped by `connect` with `mapStateToProps` to extract data from the store: @@ -124,86 +134,97 @@ To summarize the behavior of the component wrapped by `connect` with `mapStateTo | `mapStateToProps` runs when: | store `state` is `===` different | store `state` changes
or
any field of `ownProps` is different | | component re-renders when: | any field of `stateProps` is different | any field of `stateProps` is different
or
any field of `ownProps` is different | -New CodeSandbox [here](https://codesandbox.io/s/yvvqxj4p11): -- `TodoList` now connects to `todos.allIds` -- `Todo` connects to `todos.byIds` with `id` from `ownProps`, and gets `visibilityFilter` -- `Todo` contains a simpler filter in itself to determine whether to show or hide each todo -- a bunch of commented out console logs to play around and see how the component behaves +### Only Return New Object References If Needed +React-Redux does shallow comparisons to see if the `mapState` results have changed. It’s easy to accidentally return new object or array references every time, which would cause your component to re-render even if the data is actually the same. -## Keys to Good Redux Performance +Many common operations result in new object or array references being created: -### `mapStateToProps` Functions Should Be Fast +- Creating new arrays with `someArray.map()` or `someArray.filter()` +- Merging arrays with `array.concat` +- Copying values with `Object.assign` +- Copying values with the spread operator `{ ...oldState, ...newData }` -Whenever the store changes, all of the `mapStateToProps` functions of all of the connected components will run. That is a lot of callings of `mapStateToProps` at a very high frequency. `mapStateToProps` should be fast. +Put these operation in [memoized selector functions]() to ensure that they only run if the input values have changed. -Avoid doing very expensive work in `mapStateToProps` unless absolutely necessary. This includes: -**Complex filtering and transformations** -You will probably need to do complex transformations at some point. Consider first whether the transformation fits better in another place: +### Only Perform Expensive Operations When Data Changes -- Reducer is a potentially better candidate, because it concerns its own related data. -- Component’s lifecycle events such as `componentDidUpdate` or `render`, because their number of calls are cut down by `mapStateToProps` functions. -- If `mapStateToProps` is unavoidably the most suitable place for some costly computations, we suggest using memoized selectors. You may refer to the links at the end of this section for more information. +Transforming data can often be expensive (_and_ usually results in new object references being created). In order for your `mapStateToProps` function to be as fast as possible, you should only re-run these complex transformations when the relevant data has changed. -**Expensive functions such as `toJS()` of `Immutable.js`** +There are a few ways to approach this: -[Immutable.js author Lee Byron on Twitter](https://twitter.com/leeb/status/746733697093668864?lang=en) explicitly advises avoiding `toJS` when performance is a concern: +- Some transformations could be calculated in an action creator or reducer, and the transformed data could be kept in the store +- Transformations can also be done in a component's `render()` method +- If the transformation does need to be done in a `mapStateToProps` function, then we recommend using [memoized selector functions]() to ensure the transformation is only run when the input values have changed. + + +#### Immutable.js Performance Concerns + +Immutable.js author Lee Byron on Twitter [explicitly advises avoiding `toJS` when performance is a concern](https://twitter.com/leeb/status/746733697093668864?lang=en): > Perf tip for #immutablejs: avoid .toJS() .toObject() and .toArray() all slow full-copy operations which render structural sharing useless. -There's several other performance concerns to take into consideration with Immutable.js - see the list of links at the end of this post for more information. +There's several other performance concerns to take into consideration with Immutable.js - see the list of links at the end of this page for more information. -**Await async function calls** -A `mapStateToProps` function should be pure and 100% synchronous, like a reducer — state (+ownProps) in, resulting props out. -No AJAX calls, no async stuff, no dispatching actions. -### Let `mapStateToProps` Reshape the Data from the Store +## Behavior and Gotchas -`mapStateToProps` functions can, and should, do a lot more than just `return state.someSlice`. -They have the responsibility of "re-shaping" store data as needed for that component. -However, such computations should subject to its own performance requirement mentioned above. -### Do Not Return New References of Objects +### `mapStateToProps` Will Not Run if the Store State is the Same -To avoid unnecessary re-rendering, do not return new references of objects. +The wrapper component generated by `connect` subscribes to the Redux store. Every time an action is dispatched, it calls `store.getState()` and checks to see if `lastState === currentState`. If the two state values are identical by reference, then it will _not_ re-run your `mapStateToProps` function, because it assumes that the rest of the store state hasn't changed either. -Whenever the store state changes, all connected components will receive the updated store state and run `mapStateToProps` functions with them. -Odds are the changes happen only to a small slice of the store, and unrelated components should just receive the same data. +The Redux `combineReducers` utility function tries to optimize for this. If none of the slice reducers returned a new value, then `combineReducers` returns the old state object instead of a new one. This means that mutation in a reducer can lead to the root state object not being updated, and thus the UI won't re-render. -React-Redux does those shallow comparisons to avoid unnecessary re-rendering. -However, it’s easy to accidentally return new object or array references every time, which would cause your component to re-render even if the data is actually the same. -Some examples are: -- Mapping over arrays `array.map` -- Merging arrays with `array.concat` -- Copying values with `Object.assign` -- Copying values with the spread operator `{ ...oldState, ...newData }` +### The Number of Declared Arguments Affects Behavior -In `mapState` functions, it suffices to use the dot accessor for object values +With just `(state)`, the function runs whenever the root store state object is different. With `(state, ownProps)`, it runs any time the store state is different and ALSO whenever the wrapper props have changed. -```jsx -const mapStateToProps = state => { - // destructuring is ok - const { todos } = state; - return { - // do this: - user: state.user, - todos, - } +This means that **you should not add the `ownProps` argument unless you actually need to use it**, or your `mapStateToProps` function will run more often than it needs to. + + +There are some edge cases around this behavior. **The number of mandatory arguments determines whether `mapStateToProps` will receive `ownProps`**. + +If the formal definition of the function contains one mandatory parameter, `mapStateToProps` will _not_ receive `ownProps`: + +```js +function mapStateToProps(state) { + console.log(state); // state + console.log(arguments[1]); // undefined +} +const mapStateToProps = (state, ownProps = {}) => { + console.log(state); // state + console.log(ownProps); // undefined +} +``` + +It _will_ receive `ownProps` when the formal definition of the function contains zero or two mandatory parameters: + +```js +function mapStateToProps(state, ownProps) { + console.log(state); // state + console.log(ownProps); // ownProps +} + +function mapStateToProps() { + console.log(arguments[0]); // state + console.log(arguments[1]); // ownProps +} + +function mapStateToProps(...args) { + console.log(args[0]); // state + console.log(args[1]); // ownProps } ``` -### Connect More Components -Overall performance is a balance between the overhead of more `mapStateToProps` calls, and time spent by React re-rendering. -Redux subscriptions are O(n) - every additional subscriber means a bit more work every time an action is dispatched. -Fortunately, benchmarks have shown that the cost of more connected components is generally less than the cost of more wasted re-rendering. -See this FAQ ["Should I only connect my top component, or can I connect multiple components in my tree?"](https://redux.js.org/faq/reactredux#react-multiple-components) for more discussions. +