Reputation: 1235
The redux docs state that when using redux with react, so using the connect
from the react-redux
package. That there should be no need for shouldComponentUpdate
- http://redux.js.org/docs/basics/UsageWithReact.html#implementing-container-components
Redux library's connect() function, which provides many useful optimizations to prevent unnecessary re-renders. (One result of this is that you shouldn't have to worry about the React performance suggestion of implementing shouldComponentUpdate yourself.)
However my component is unnecesarily updated because I am returing a new array every time even though the contents of the array is the same. Here is my code:
const AssetManagerSmart = connect(
function(state) {
const { assets } = state;
return {
assetIds: assets.map( asset => asset.id )
}
}
)
See this assetIds
, it is a map of an array from the redux state. This is causing my component to re-render. To simplify the code above, we can imagine assetIds
is assigned to a new shallowly equal array everytime, like this:
const AssetManagerSmart = connect(
function(state) {
const { assets } = state;
return {
assetIds: ['hi']
}
}
)
How come the docs say I should not need shouldComponentUpdate
when I am encountering the above? The above I see everyone doing, even in the "usage with react" section on official redux docs.
Upvotes: 2
Views: 558
Reputation: 191946
When connect is in the default pure
mode it performs several checks that aim to avoid invoking mapStateToProps
, and if it was invoked, prevent rerender of the wrapped component if nothing changed.
To check if it should run mapStateToProps
(and mergeProps
) connects makes 2 equality checks:
areStatesEqual
- checks if the entire state changed using strict equality. Whenever the store changes this will return false
. However, you can override it, to check if a certain piece of the store changed. In your case you might want to check if assets
have changed:
(next, prev) => prev.assets === next.assets
areOwnPropsEqual
- makes a shallow equality checks to see if the props supplied to the component returned from connect have changed. Since you don't use props, you can theoretically override this to always return true
, however this will prevent rerenders on prop changes completely.
If mapStateToProps
is invoked 2 checks are performed to see if it should rerender the wrapped component:
areStatePropsEqual
- shallow equality between the current and previous results of mapStateToProps
. This will check if prev { assetIds: [] }
equals { assetIds: [] }
, and since the assetsIds
array changed will return false
.
areMergedPropsEqual
- shallow equality between the current and previous results of mergeProps
- the combined ownProps
, mapStateToProps
, mapDispatchToProps
.
So if your state changes or your props change, mapStateToProps
will be called, and the assetIds
will recalculate. You can override the the areStatesEqual
and areOwnPropsEqual
to whitelist only specific changes that should invoke mapStateToProps
.
The result of mapStateToProps
will be shallow checked vs. the previous result. You can override the check to actually see if the items in the assetIds
have changed, but that may be expensive, and you would also need to the same in areMergedPropsEqual
. An easier options is to use a memoized selector.
Memoized Selectors
A selector can take a piece of the store, and compute derived data. A memoized selector will return the same result (without performing the computation) as long as the relevant part of the state did not change. Since the result is the same array (not a new array with similar values), this will pass the areStatePropsEqual
and areMergedPropsEqual
checks (unless something else changed).
Reselect is a library that helps you in creating memoized selectors.
This is how you can create a memoized selector for the assets' ids (not tested):
import { createSelector } from 'reselect'
const getAssets = ({ assets }) => assets; /** simple not memoized selector **/
const getAssetIds = createSelector( /** memoized selector **/
getAssets,
(assets) => assets.map(asset => asset.id)
);
Upvotes: 2