Here we’ll show you an intricate guide on how to correctly apply useSelector to efficiently render components, how to set up the custom compare function to avoid rendering and how to memoize components, helping performance by reducing the number of times the logic inside the selector is triggered
Written by:
Albert Vinyals, iOS Developer at Nuvolar |
This article aims to describe the best options the Mobile team at Nuvolar considers when using functional components that connect to the Redux Store thanks to the useSelector hook.
After previous discussions, the team decided to set a goal: to write this “Best Practices” article to make sure that, from now on, we all use the same, correct and consistent, approach, which we are explaining in detail here.
1. useSelector: The hook which allows you to extract data from the Redux Store.
The selector is approximately equivalent to mapStateToProps and will be called when the store state changes.
Please note that useSelector will do a reference comparison (===) of the previous selector result.
If the result is different the component will be re-rendered. For primitive values, this will have no major impact but please take this into consideration when working with objects (including arrays).
useSelector is called every time an action is dispatched to the store.
1a. Destructuring: Best way to avoid unnecessary rendering
As mentioned previously, the useSelector will use strict reference equality to decide whether a render has to be triggered or not. Imagine the following scenario:
If the user’s state object reference changes (even maintaining the values for name and photo keys) the component will re-render (it is comparing 2 different objects references).
In order to avoid this unnecessary render please consider the following approach:
This will only render if the values for the keys name and photo change. useSelector will be comparing primitives in this case, so only if the string value is different the render will be triggered.
There is no inconvenience in creating a new useSelector for each value. This is what the documentation states:
You may call useSelector() multiple times within a single function component. Each call to useSelector() creates an individual subscription to the Redux Store. Because of the React update batching behavior used in React Redux v7, a dispatched action that causes multiple useSelector()s in the same component to return new values should only result in a single re-render.
1b. Comparing objects
In case you have the need to compare objects and you can not use values instead, there are still 2 options one can use.
Remember, the main goal is to avoid re-rendering when it is not strictly necessary (because a value used for rendering purposes changed).
- Custom compare function:
You can specify a function that will be used to compare the result of the selector. Feel free to implement your own or to use the already existing shallowEqual.
Please bear in mind that this may hit the performance since shallowEqual may be heavy in terms of processing (depending on the object shape).
- Memoizing:
Use reselect or a similar library that returns multiple values in one object, but only returns a new object when one of the values has changed. On top of that, this approach helps performance by reducing the number of times the logic inside the selector is triggered.
2. React.memo: helping memoize with parent components
Even when you are properly preventing your component to re-render based on the proper usage of useSelector there are still situations in which it can get rendered unnecessarily.
This may happen when the parent component re-renders (parent state changes).
In order to fix this issue React.memo helps memoize the component and return the cached version if the props have not changed.
Imagine the following scenario:
When “increment()” is triggered, the state changes and this will re-render the component and all its children, UserComponent included.
There is no need for this since the component will be re-rendered with the exact same layout and content.
Avoid this by using the React.memo hoc.
Afterwards the component returned, when the parent is re-rendered, will be the memoised version.
3. Additional Resources
- https://react-redux.js.org/api/hooks#useselector
- https://redux.js.org/usage/deriving-data-selectors#deriving-data
- https://github.com/reduxjs/reselect
- https://www.w3schools.com/react/react_memo.asp
- https://reactjs.org/docs/react-api.html#reactmemo
About Nuvolar:
We are a digital innovation consulting company dedicated to one unique purpose: helping businesses adopt world-class software solutions on the cloud so they can succeed!