Derived state and using Selectors in React and Redux
22 May, 2020
5 mins
When working with React our approach is always towards minimizing the no. of re-renders to gain performance. As we all are aware of the fact that in React render depends on two things state and props, so to reduce the no. of re-renders we have to start by controlling the state and props updates, by avoiding unnecessary or redundant updates.
React has always been careful with this practice and as a solution, it provides us with two APIs for class and functional component PureComponent and React.memo.
React PureComponent makes sure that a component is not updated until and unless there is an update in its props and state.
React.memo is a higher order function, React.memo only checks for prop changes.
Note : Both these APIs use shallow comparison.
So there is that. 🥱
Using State Management Library
Often when developing a React application we end up integrating redux or any other state management library to have a shared state across the App.
Integration of Redux is not cheap, it comes with a cost and it is highly recommended that if possible we should avoid using Redux in a small application and rather use React context API for managing the state, after all Redux is built over React.context API.
So, now that we are using Redux what we can do to use it smartly 🧐
- by cutting the cost where we can,
- by using tested patterns and solutions.
Therefore what we have discussed above about React components with redux being part of our state management library same responsibility falls on it. Redux should also avoid contributing any updates to props that are redundant or lead to an unrequired recalculation of state. If you don't have experience with redux, I encourage you to go through their getting started guide.
How It can be Achieved
Firstly, you can start by using an immutable library like immutable.js
or immer
for your redux states.
Moving on 🏃♂️.
In redux rather than passing everything from store to component and calculating that data in our components and state, we can first derive the required state at the redux layer in mapStateToProps
.
For example, calculating user name from multiple user fields. Lets abstract that logic from component to mapStateToProps.
1const mapStateToProps = (state) => {2 let userTitle;3 if (state.user) {4 if (state.user.gender === 'Male') {5 userTitle = 'Mr.';6 } else if (state.user.maritalStatus === 'Married') {7 userTitle = 'Mrs.';8 } else {9 userTitle = 'Miss';10 }11 }12 const username = `${userTitle} ${state.user.firstName} ${state.user.lastName}`;1314 return {15 username,16 };17};
But using the suggested flow of data manipulation introduces code smell, also the separation of concern is an issue now as mapStateToProps
is doing more than just mapping the store state to props.
Introduction to Selectors.
We can use selectors for the derivation of data. Using selectors adds the benefits of reusing the derivation state logic across the app. Selectors are JS functions used to refactor our code, nothing special here, it is more of a pattern which makes selector so popular.
- Selectors can compute derived data, allowing Redux to store the minimal possible state.
- Selectors are efficient. A selector is not recomputed unless one of its arguments changes.
- Selectors are composable. They can be used as input to other selectors.
Lets us refactor above used example to use a selector :
1// index.js2const mapStateToProps = (state) => {3 return {4 userName: selectUserName(state.user),5 };6};78// selector.js9export const selectUserName = (user) => {10 let userTitle;11 if (user) {12 if (user.gender === 'Male') {13 userTitle = 'Mr.';14 } else if (user.maritalStatus === 'Married') {15 userTitle = 'Mrs.';16 } else {17 userTitle = 'Miss';18 }19 }2021 return `${userTitle} ${user.firstName} ${user.lastName}`;22};
By introducing selectors we have abstracted out the logic for username
, now anywhere in our application where we need username we can use the selectUserName
.
There is still an issue with above code. 🤷♂️
If there is an update in the redux state tree due to any other reducer, the selector will re-calculate the value which will result in re-renders. If the state tree is large, or the calculation is expensive, repeating the calculation on every update may cause performance problems.
To solve the above problem, we can memoize our selector, in that case, the selector will re-calculate new value only if its arguments change.
Using Reselect
For using the memoized selector and other patterns around selector we will now use the reselect library by redux. To explore reselect APIs we will be using todos example. I know right, another todos example nothing innovative here. Sorry.
Let's define a memoized selector named getVisibleTodos
using createSelector
from reselect.
1// index.js2const mapStateToProps = (state) => {3 return {4 todos: getVisibleTodos(state),5 };6};78// selectors.js9import { createSelector } from 'reselect';1011const getVisibilityFilter = (state) => state.visibilityFilter;12const getTodos = (state) => state.todos;1314export const getVisibleTodos = createSelector(15 [getVisibilityFilter, getTodos],16 (visibilityFilter, todos) => {17 switch (visibilityFilter) {18 case 'SHOW_ALL':19 return todos;20 case 'SHOW_COMPLETED':21 return todos.filter((t) => t.completed);22 case 'SHOW_ACTIVE':23 return todos.filter((t) => !t.completed);24 }25 }26);
In the example above, getVisibilityFilter and getTodos are input-selectors. They are created as ordinary non-memoized selector functions because they do not transform the data they select. getVisibleTodos, on the other hand, is a memoized selector. It takes getVisibilityFilter and getTodos as input-selectors and a transform function that calculates the filtered todos list.
I have implemented the above example, so run can test and play within codesandbox 👩💻.
To understand the benefits of selectors, open the console in codesandbox and toggle the theme for a couple of times, what you will notice after reading the console is that, calculation of todos and rendering don't occur in case you use selector function in mapStateToProps
.
Cool, so we are almost done now. ⏳
Selectors, as previously mentioned, are composable, a memoized selector can itself be an input-selector to another memoized selector.
To explore all the APIs provided by reselect
please visit the docs, they have detailed examples and FAQ section.
Caveats
- A selector created with createSelector has a cache size of 1 and only returns the cached value when its set of arguments is the same as its previous set of arguments.
- The default equalityCheck function checks for changes using reference equality, in default memoize function. Custom equality check example..
- Best with the immutable redux store.
Conclusion
So, now we know when and how we can use a selector. Remember selector is nothing but a JS function, you can use it not only for the redux state but anywhere in your code where you see it fits. Also, I hope now you have a better understanding of selectors and you can make a decision on whether you require reselect library in your project or not.