Avoiding Re-Renders in React and optimising MapStateToProps with Reselect.
Using Memoization in React Applications.
Avoid unnecessary Re-renders and unnecessary mapStateToProps computations in one go?
We will try to answer why and how to create your own Memoized Selectors?
First, Let’s start with what is Memoization?
In computing, memoization is an optimization technique used primarily to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again.
In lay man’s terms:
Memoization is derived from the Latin word “memorandum” (“to be remembered”), usually truncated as “memo”, and thus carries the meaning of “turning [the results of] a function into something to be remembered.”
Your programs don’t have memory.
For example, if an expensive function Func(n, m) is called with inputs, 2 and 3, and it takes 1 ms to compute the output to 3000.
Func(2, 3)= 3000; // Functions don't have memory
At the next instance when the function is called again with inputs — 2 and 3 it will re-compute for 1 ms to arrive at the same output 3000.
Memoization gives programs the ability to create a memos.
It is like ,“Hey function, func(n, m), if you get inputs 2 and 3, just return the value 3000!” — Memo
Memoized selectors is like giving your react apps which do not have memory ability to write memo’s to itself.
How to Optimise React apps with Memoization?
Lets start with this FictionalComponent
Our requirement is to create this UI.
We can add centre and then select one of the centre.
If the Centre selected is 0 and number of Centres is greater than 1, we need to disable the action buttons. Otherwise, they should be enabled
if Centre is equal to 0 and number of Centre > 1 disable all buttons.
This component has two Props max_centers and centerId so anytime max_centers or centerId will change component will re-render.
Let’s look at the props of FictionalComponent w.r.t time
Initially, max_centers were 4 and centerId was 0, all the buttons are rendered and are disabled since disableAll is true.
After some time, prop max_centers changed to 5.
Now, our FictionalComponent will re-render even though disableAll is not changing. FictionalComponent’s prop is changed, so it will re-render!
How to avoid this Unnecessary Re-render ? It’s pretty easy!
Since both the props compute disableAll value, we can merge both the props in mapStateToProps function.
Since we expose only the disableAll prop, only when the computed value of disableAll changes from true to false or vice-versa will the component re-render.
So, now even though max_centers changes from 5 to 7 disableAll prop will not change (will still remain true).
Component’s prop is not changed, so it will not re-render.
Perfect! Problem solved.
Well, not really!
If we deep dive in to how react-redux implements mapStateToProps, when ever a new state tree is computed — it calls the mapStateToProps function on all the connected Components. (Oh, that is why we connect Components! )
Hence, even if state.someKey is changed in Redux, your mapStateToProps will recompute the disableAll value as mapStateToProps function will called on all the connected components with the new State.
We want to merge props but we also want to avoid unnecessary computations in mapStateToProps, every time something changes in your State.
For this, we write a memoized Selector in mapStateToProps.
Now, you have your own selector function.
When ever the state tree changes and max_centers or centerId does not change, it will not go in the If condition. It will simply return the value from the cache.
If the centerId or max_centers is changed, it will go in if condition and update the cache with new values for input and output and return the new output.
This is just an example of how something like this can be implemented on your own. Problems with implementing something like this by own are many.
- For every selector you would have to write a lot more boilerplate code.
- Extending caching, composing selectors and creating separate instances for separate components. If you want to do these as well, you will have to write your own custom wrapper which will require good time.
Why re-invent the wheel if you can get all of the above mentioned and much more with Reselect?
With Reselect your component will look like this.
Now as we are exposing only one prop to our FictionalComponent, only when the computed value for disableAll prop changes from true to false or vice-versa will the component re-render.
Also, even if state.someKey is changed in Redux and mapStateToProps is called on all the connected components with the new State.
Since Reselect Memoizes,
It will not recompute disableAll in disableAllSelector as max_centers or centerId has not changed.
Reselect also makes sure you do not write a lot of boiler-plate code to achieve this.
- You can also compose selectors easily.
2. Customise caching limit of selectors.
3. Customise selector to be reused by different components by creating separate instances of the selectors and much more.
Customise selector to be reused by different components by creating separate instances of the selectors and much more. What does this mean?
Let’s change the UI to understand this.
Suppose, we were to develop something like this.
Two Centre Lists “A” and “B” and Two Fictional Components one for “A” and one for “B”
In this case, we have two instances of Fictional Component, but only one instance of our Memoized Selector.
Note: By default we memoize only one value.
When this UI renders the last value stored in the “memo” we have is for inputs 2 and 0.
When a new centre is added in the B list, selector function in “A” instance of Fictional Component will compute, since its inputs are (5, 0) and cached value is for (2, 0) and then value for (5, 0) will be cached and returned. After that if a centre in A list is added, the selector of B Instance of fictional Component will also compute since now value for (5,0) is cached but input is (3,0)
What we want here is Two Memos, One dedicated for each instance of Fictional Component!
Behold the Special MapStateToProps Mode!
If the mapStateToProps argument supplied to mapStateToProps returns a function instead of an object, it will be used to create an individual mapStateToProps function for each instance of the container.
Here, we supply “function instead of an object” to connect as “mapStateToProps” and it creates an individual mapStateToProps function for each instance of the container
Before returning the inner function which takes the state., we create a selector instance from MakeSelector function so it creates a one instance of Memoized selector for every instance parent component.
This means A instance of Fictional Component will always use A instance of memoized selector and B instance of Component will always use B instance of memoized selector.
BAM! There we go.
If you liked reading this, don’t forget to clap. You can also follow me on on twitter @karanjariwala47 for java-script or react updates.
Read more at https://github.com/reactjs/reselect
Follow us on twitter for regular updates. If you liked this article, please hit the applause icon to recommend it. Looking for 50+ applauses. This will help other Medium users find it.