Redux is to me the greatest React companion, easy to use, quite fast to understand and it comes with a very rich ecosystem. The issue many developers face when building a real application is a performance reduction as the project grows, using more and more connected components and dispatching many actions.
The trap comes from the fact every connected component’s
mapStateToProps function is called after each dispatched action and reducers computations, because Redux does not know anything about parts of the store your components are interested in.
The official Redux documentation advices the use of Reselect library, but it is very opinionated about the structure of your state selectors and the move may seem unreachable for an existing application with tons of selectors and connected components.
At this step do we need to rewrite the entire app or try to limit the number of actions and connected components?
The pattern I will present you here can be applied to an existing project with only minor modifications of your components and reducers. Although it would not apply to a few specific or exotic components, it avoids most of the useless computations, giving a dramatic breath of air to your application.
A typical structure of a Redux store is often quite well divided at the first level with independent reducers in charge of their own part of the state:
Most actions impact - if not only one part - a very few number of that state parts. And it is the same for connected components, they are often only interested in a few parts of the state.
So what if we could just memoize
mapStateToPropsby allowing each component to declare for which first level state parts modifications they want to have their mapStateToProps processed?
The hardest part of memoization, when you are not dealing with pure functions, is to find a way to determine if you need or not to process the computation, but a way that has a lower cost than the computation itself 😅.
The easiest solution comes from the observation that it is very easy to specify at the component level which first level parts of the state it is consuming, and it will be the only modification you will have to do, keeping the same
Let’s start from a standard reducer:
We will modify the way it works to version its content:
Instead of returning the modified state in each case, we just feed a
newState variable, break, and if it is not null after the switch we increment the version and return it. If not, we just return the original state and the version value remains the same.
Then we have an easy way to tell which part of the state has changed. So let’s write a new selector function that returns a simple combined signature of one or more parts of the state:
The function takes the state as a parameter like any state selector. The second parameter can either be a string or an array of string. It only considers the first level parts of the state, but we could imagine a more complex system that would handle versioning at other levels too, depending on your state structure and needs.
The versioning and signature system could also be designed by keeping references to desired parts of the state, but having references to many old versions of state parts could be very memory consuming, preventing garbage collection to operate.
A typical connected component has a
mapStateToProps function, passed as a parameter to the wrapping connect function:
We want our optimization to be as simple as possible to implement. So the only modifications we will do in our component if it is interested in parts 1 & 3 of the state will be:
It basically means that the
mapStateToProps function should be processed only if state.parts1 or state.parts3 are modified, otherwise it should use the previously cached result of the
mapStateToProps parameter passed to the
connect function returns a function instead of an object, the function will be used to generate the
mapStateToProps function of each instance of the component. This is a very important point to understand as we need each instance of the component to cache its own results of the
So basically, our memoization function base will look like this:
It does nothing usefull at this step but returning a dedicated instance of the
mapStateToProps function to each instance of the component.
Now we can add our layer of memoization:
How does it work ?
cachedSignatureholds the signature of the state parts we want to react to their changes
cachedResultholds the result of the
memoizedMapStateToPropscompares the current signature of state parts computed in
getStateSignaturewith the last one stored in 0
cachedResultif needed, then always returns
That’s all ! It is so light that I have not even tried to have you install it as a new library :-)
- Start to implement on the persistent connected components in your application to maximize the performance profits. (Header, footer, menus, etc.)
- Do not forget to update the state parts list when you update your component’s
Do not hesitate to give feedback about improvements or performance gains you have achieved with the pattern !
EDIT / The better way
Writing about the way you do things has the benefit to make you read the docs again, evaluate the pertinence of your work and do some research.
The redux’s connect function has in fact a few other parameters among which
areStatesEqual which allows to precise if the state has changed or not. It is well explained by Julien De Luca in this story :
The most unknown redux performance trick
Regarding optimizing redux, there are some well known techniques: memoizing, using reselect for example.
So I have realized that adding a versioning on state parts was useless when you have the opportunity to access previous state to do the arbitration. Memoization itself of the
mapStateToProps function is no longer necessary.
Here is a connect wrapper I now use to facilitate its usage:
A the component level, it is quite straightforward, and the performance is even better as parts of the state can be pointed very precisely :
The third parameter can be:
- a string if you are interested in one state path
- an array of string if the component is binded to multiple parts
- each string can be a root path (ex: ‘part1’) or a dot separated deepest path (ex: ‘part1.subpart2.thing2)