I am trying to understand the mechanics of mapStateToProps when it returns a function.
So I can't find much documentation except a short excerpt from the Redux docs that says in advance cases by returning a function, each instance will get its own memoized mapStateToProps and another user saying that this is an optimisation that prevents mapStateToProps being called for any parent prop changes.
So this seems great for list items, where I would't want to re-render large list of items for any changes that doesn't affect an item.
So the part that confuses me is that mapStateToProps won't be called for any parent prop changes, does that mean in order for an individual list 'Item' to be re-rendered it needs to be a smart connected component for it to pick up changes it cares about and re-render? Or does that mean it will never re-render for this particular Item instance?
Update:
Wanted to clarify that I am talking specifically about the factory function version of mapStateProps.
Here is the feature I am talking about, taken from React-Redux docs:
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 and the tests it adds for more details. Most apps never need this.
And paragraph taken from this article:
https://medium.com/@cvetanov/redux-mapstatetoprops-optimization-5880078a8a7a
If redux receives an implementation which returns a function it performs a closure for wrapping the own props of the component and therefore bypasses the invocation of the mapStateToProps every time the component changes it’s props received from the parent component. It creates a so called purePropsSelector. How this is done can be seen here.
Update 2:
Right I am investigating the article that mentions the skipping, it seems to be when you wrap the own props in a closure and return a function that only uses the state. So it prevents the mapStateToProps being invoked when the parent props changes for each 'connected' child item.
This is taken from that medium article I read above:
function mapStateToPropsFactory(initialState, ownProps) {
// a closure for ownProps is created
// this factory is not invoked everytime the component
// changes it's props
return function mapStateToProps(state) {
return {
blogs:
state.blogs.filter(blog => blog.author === ownProps.user)
};
};
}
export default connect(mapStateToPropsFactory)(MyBlogs);
Your mapStateToProps function should return a plain object that contains the data the component needs: 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.
You can definitely use mapStateToProps with a functional component, the same way you would with a class component.
mapStateToProps() is a function used to provide the store data to your component. On the other hand, mapDispatchToProps() is used to provide the action creators as props to your component.
The mapStateToProps and mapDispatchToProps deals with your Redux store's state and dispatch , respectively. state and dispatch will be supplied to your mapStateToProps or mapDispatchToProps functions as the first argument.
Every mapStateToProps
function will always be called if the state changes. There is no mechanism included in redux that prevents mapStateToProps
from being called.
From the docs for
connect()
:
mapStateToProps(state, [ownProps]): stateProps] (Function)
: If this argument is specified, the new component will subscribe to Redux store updates. This means that any time the store is updated, mapStateToProps will be called.
What you want to prevent (usually by using selectors) is that expensive computations happening inside mapStateToProps
will be repeated on every state update even if they produce the same result.
Or does that mean it will never re-render for this particular Item instance?
It will re-render as usual if any props received by the connected component change. The point is to prevent mapStateToProps
from doing expensive calculations. mapStateToProps
is dumb. It does its calculations and passes them to the connected component as props. The component then checks if the props differ from the previous ones and decides to re-render based on that.
Consider this mapStateToProps
function which uses a selector getVisibleTodos
:
const mapStateToProps = state => {
return {
todos: getVisibleTodos(state.todos, state.visibilityFilter)
}
}
Selectors memorize the result of a call and will simply return that result on subsequent calls as long as the input parameters do not change. The selector in this example only gets its input from the redux state. As long as state.todos
and state.visibilityFilter
do not change it can use the memorized result from the last call and does not need to recompute something.
Now consider this another example:
const TodoList = ({id, todos}) => (
<ul id={id}>
{todos.map(/* ... */)}
</ul>
);
const mapStateToProps = (state, props) => {
return {
todos: getVisibleTodos(state, props)
}
}
export default connect(mapStateToProps)(TodoList);
This time the selector additionally takes the components own props as input. This is problematic because if we use two instances of the connected TodoList
and render it like
<TodoList id="list1" />
<TodoList id="list2" />
this will cause mapStateToProps
to be called twice on state updates, one time for each TodoList
instance. And both times it will receive different props. One time with {id: 'list1'}
and the second time with {id: 'list2'}
. But both components share the same selector. This will lead to a re-computation by the selector even if the todos
for each individual TodoList
did not change. Now the mapStateToProps
function that returns a function by itself comes into play:
const makeMapStateToProps = () => {
const getVisibleTodos = makeGetVisibleTodos() // this creates a new selector function
const mapStateToProps = (state, props) => {
return {
todos: getVisibleTodos(state, props)
}
}
return mapStateToProps
}
This creates an individual mapStateToProps
function for each instance of TodoList
which has its own selector so that it memorizes the props for each instance individually and will only re-compute if props for the instance it was created for change. This will solve the issue from the previous example.
So tl;dr;: When a selector inside mapStateToProps
takes the own props of the connected component as a parameter you need to use the mapStateToProps
as a factory to allow for per-instance memorization.
You can find a more detailed explanation with an example under Comuting Derived Data in the redux docs.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With