Skip to content

Commit 979768b

Browse files
committed
Restructure the mapState page content
1 parent 1d9069a commit 979768b

File tree

1 file changed

+126
-104
lines changed

1 file changed

+126
-104
lines changed

docs/connect-extracting-data-with-mapStateToProps.md

Lines changed: 126 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -16,106 +16,116 @@ As the first argument passed in to `connect`, `mapStateToProps` is used for sele
1616

1717
`mapStateToProps` should be defined as a function:
1818

19-
```jsx
20-
function mapStateToProps(state[, ownProps])
19+
```js
20+
function mapStateToProps(state, ownProps?)
2121
```
2222
23-
It should take `state`, optionally `ownProps`, and return a plain object containing the data that the connected component needs.
23+
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.
24+
25+
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`.
26+
27+
**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.
2428
2529
### Arguments
2630
27-
- `state`
31+
#### `state`
32+
33+
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".)
2834
29-
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`.
35+
The `mapStateToProps` function should always be written with at least `state` passed in.
3036
31-
```jsx
37+
```js
3238
// TodoList.js
33-
// ...
3439

35-
const mapStateToProps = state => {
40+
function mapStateToProps(state) {
3641
const { todos } = state;
3742
return { todoList: todos.allIds };
3843
};
3944

4045
export default connect(mapStateToProps)(TodoList);
4146
```
4247
43-
- `ownProps` (optional)
48+
#### `ownProps` (optional)
4449
45-
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.
50+
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`.
4651
47-
```jsx
52+
```js
4853
// Todo.js
4954

50-
const mapStateToProps = (state, ownProps) => {
55+
function mapStateToProps(state, ownProps) {
5156
const { visibilityFilter } = state;
5257
const { id } = ownProps;
5358
const todo = getTodoById(state, id);
5459

5560
// component receives additionally:
5661
return { todo, visibilityFilter };
5762
};
63+
64+
// Later, in your application, a parent component renders:
65+
<ConnectedTodo id=123} />
66+
// and your component receives props.id, props.todo, and props.visibilityFilter
5867
```
5968
60-
**The arity of `mapStateToProps` affects its behavior:**
69+
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.
70+
71+
### Return
6172
62-
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.
73+
Your `mapStateToProps` function should return a plain object that contains the data the component needs:
6374
64-
**Mandatory number of arguments determines whether `mapStateToProps` will receive `ownProps`:**
75+
- Each field in the object will become a prop for your actual component
76+
- The values in the fields will be used to determine if your component needs to re-render
6577
66-
If the formal definition of the function contains one mandatory parameter, `mapStateToProps` will _not_ receive `ownProps`:
78+
For example:
6779
68-
```jsx
80+
```js
6981
function mapStateToProps(state) {
70-
console.log(state); // state
71-
console.log(arguments[1]); // undefined
72-
}
73-
const mapStateToProps = (state, ownProps = {}) => {
74-
console.log(state); // state
75-
console.log(ownProps); // undefined
82+
return {
83+
a : 42,
84+
todos : state.todos,
85+
filter : state.visibilityFilter
86+
}
7687
}
88+
89+
// component will receive: props.a, props.todos, and props.filter
7790
```
7891
79-
It _will_ receive `ownProps` when the formal definition of the function contains zero or two mandatory parameters:
8092
81-
```jsx
82-
const mapStateToProps = (state, ownProps) => {
83-
console.log(state); // state
84-
console.log(ownProps); // ownProps
85-
}
93+
> 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.
8694
87-
function mapStateToProps() {
88-
console.log(arguments[0]); // state
89-
console.log(arguments[1]); // ownProps
90-
}
9195
92-
const mapStateToProps = (...args) => {
93-
console.log(args[0]); // state
94-
console.log(args[1]); // ownProps
95-
}
96-
```
96+
## Usage Guidelines
9797
98-
### Return
99-
In the most common usages, you should let your `mapStateToProps` return a plain object that contains the data the component needs:
10098
101-
- it will be merged into the component’s props
102-
- it will be used to decide whether the component will re-render
99+
### Let `mapStateToProps` Reshape the Data from the Store
103100
101+
`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.
104102
105-
> 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.
106103
107-
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.
104+
### Use Selector Functions to Extract and Transform Data
108105
109-
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:
106+
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.)
107+
108+
109+
### `mapStateToProps` Functions Should Be Fast
110+
111+
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.
112+
113+
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.
114+
115+
116+
### `mapStateToProps` Functions Should Be Pure and Synchronous
117+
118+
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`.
119+
120+
121+
122+
## `mapStateToProps` and Performance
123+
124+
125+
### Return Values Determine If Your Component Re-Renders
126+
127+
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.
110128
111-
```jsx
112-
const mapStateToProps = state => {
113-
// re-renders even if the list may remain the same
114-
return {
115-
completedTodos: state.todos.filter(todo => todo.completed)
116-
};
117-
}
118-
```
119129
120130
To summarize the behavior of the component wrapped by `connect` with `mapStateToProps` to extract data from the store:
121131
@@ -124,86 +134,97 @@ To summarize the behavior of the component wrapped by `connect` with `mapStateTo
124134
| `mapStateToProps` runs when: | store `state` is `===` different | store `state` changes <br /> or <br />any field of `ownProps` is different |
125135
| component re-renders when: | any field of `stateProps` is different | any field of `stateProps` is different <br /> or <br /> any field of `ownProps` is different |
126136
127-
New CodeSandbox [here](https://codesandbox.io/s/yvvqxj4p11):
128137
129-
- `TodoList` now connects to `todos.allIds`
130-
- `Todo` connects to `todos.byIds` with `id` from `ownProps`, and gets `visibilityFilter`
131-
- `Todo` contains a simpler filter in itself to determine whether to show or hide each todo
132-
- a bunch of commented out console logs to play around and see how the component behaves
138+
### Only Return New Object References If Needed
133139
140+
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.
134141
135-
## Keys to Good Redux Performance
142+
Many common operations result in new object or array references being created:
136143
137-
### `mapStateToProps` Functions Should Be Fast
144+
- Creating new arrays with `someArray.map()` or `someArray.filter()`
145+
- Merging arrays with `array.concat`
146+
- Copying values with `Object.assign`
147+
- Copying values with the spread operator `{ ...oldState, ...newData }`
138148
139-
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.
149+
Put these operation in [memoized selector functions]() to ensure that they only run if the input values have changed.
140150
141-
Avoid doing very expensive work in `mapStateToProps` unless absolutely necessary. This includes:
142151
143-
**Complex filtering and transformations**
144152
145-
You will probably need to do complex transformations at some point. Consider first whether the transformation fits better in another place:
153+
### Only Perform Expensive Operations When Data Changes
146154
147-
- Reducer is a potentially better candidate, because it concerns its own related data.
148-
- Component’s lifecycle events such as `componentDidUpdate` or `render`, because their number of calls are cut down by `mapStateToProps` functions.
149-
- 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.
155+
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.
150156
151-
**Expensive functions such as `toJS()` of `Immutable.js`**
157+
There are a few ways to approach this:
152158
153-
[Immutable.js author Lee Byron on Twitter](https://twitter.com/leeb/status/746733697093668864?lang=en) explicitly advises avoiding `toJS` when performance is a concern:
159+
- Some transformations could be calculated in an action creator or reducer, and the transformed data could be kept in the store
160+
- Transformations can also be done in a component's `render()` method
161+
- 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.
162+
163+
164+
#### Immutable.js Performance Concerns
165+
166+
Immutable.js author Lee Byron on Twitter [explicitly advises avoiding `toJS` when performance is a concern](https://twitter.com/leeb/status/746733697093668864?lang=en):
154167
155168
> Perf tip for #immutablejs: avoid .toJS() .toObject() and .toArray() all slow full-copy operations which render structural sharing useless.
156169
157-
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.
170+
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.
158171
159-
**Await async function calls**
160172
161-
A `mapStateToProps` function should be pure and 100% synchronous, like a reducer — state (+ownProps) in, resulting props out.
162-
No AJAX calls, no async stuff, no dispatching actions.
163173
164-
### Let `mapStateToProps` Reshape the Data from the Store
174+
## Behavior and Gotchas
165175
166-
`mapStateToProps` functions can, and should, do a lot more than just `return state.someSlice`.
167-
They have the responsibility of "re-shaping" store data as needed for that component.
168-
However, such computations should subject to its own performance requirement mentioned above.
169176
170-
### Do Not Return New References of Objects
177+
### `mapStateToProps` Will Not Run if the Store State is the Same
171178
172-
To avoid unnecessary re-rendering, do not return new references of objects.
179+
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.
173180
174-
Whenever the store state changes, all connected components will receive the updated store state and run `mapStateToProps` functions with them.
175-
Odds are the changes happen only to a small slice of the store, and unrelated components should just receive the same data.
181+
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.
176182
177-
React-Redux does those shallow comparisons to avoid unnecessary re-rendering.
178-
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.
179183
180-
Some examples are:
181184
182-
- Mapping over arrays `array.map`
183-
- Merging arrays with `array.concat`
184-
- Copying values with `Object.assign`
185-
- Copying values with the spread operator `{ ...oldState, ...newData }`
185+
### The Number of Declared Arguments Affects Behavior
186186
187-
In `mapState` functions, it suffices to use the dot accessor for object values
187+
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.
188188
189-
```jsx
190-
const mapStateToProps = state => {
191-
// destructuring is ok
192-
const { todos } = state;
193-
return {
194-
// do this:
195-
user: state.user,
196-
todos,
197-
}
189+
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.
190+
191+
192+
There are some edge cases around this behavior. **The number of mandatory arguments determines whether `mapStateToProps` will receive `ownProps`**.
193+
194+
If the formal definition of the function contains one mandatory parameter, `mapStateToProps` will _not_ receive `ownProps`:
195+
196+
```js
197+
function mapStateToProps(state) {
198+
console.log(state); // state
199+
console.log(arguments[1]); // undefined
200+
}
201+
const mapStateToProps = (state, ownProps = {}) => {
202+
console.log(state); // state
203+
console.log(ownProps); // undefined
204+
}
205+
```
206+
207+
It _will_ receive `ownProps` when the formal definition of the function contains zero or two mandatory parameters:
208+
209+
```js
210+
function mapStateToProps(state, ownProps) {
211+
console.log(state); // state
212+
console.log(ownProps); // ownProps
213+
}
214+
215+
function mapStateToProps() {
216+
console.log(arguments[0]); // state
217+
console.log(arguments[1]); // ownProps
218+
}
219+
220+
function mapStateToProps(...args) {
221+
console.log(args[0]); // state
222+
console.log(args[1]); // ownProps
198223
}
199224
```
200225
201-
### Connect More Components
202226
203-
Overall performance is a balance between the overhead of more `mapStateToProps` calls, and time spent by React re-rendering.
204-
Redux subscriptions are O(n) - every additional subscriber means a bit more work every time an action is dispatched.
205-
Fortunately, benchmarks have shown that the cost of more connected components is generally less than the cost of more wasted re-rendering.
206-
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.
227+
207228
208229
<!--
209230
## Next Up
@@ -217,12 +238,13 @@ See this FAQ ["Should I only connect my top component, or can I connect multiple
217238
**Tutorials**
218239
219240
- [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/)
241+
- [Idiomatic Redux: Using Reselect Selectors for Encapsulation and Performance](https://blog.isquaredsoftware.com/2017/12/idiomatic-redux-using-reselect-selectors/)
220242
221243
**Performance**
222244
223245
- [Lee Byron's Tweet Suggesting to avoid `toJS`, `toArray` and `toObject` for Performance](https://twitter.com/leeb/status/746733697093668864)
224246
- [Improving React and Redux performance with Reselect](https://blog.rangle.io/react-and-redux-performance-with-reselect/)
225-
- [Relevant links to immutable data](https://github.com/markerikson/react-redux-links/blob/master/react-performance.md#immutable-data)
247+
- [Immutable data performance links](https://github.com/markerikson/react-redux-links/blob/master/react-performance.md#immutable-data)
226248
227249
**Q&A**
228250

0 commit comments

Comments
 (0)