Reselect — A Phase To Make A React-Redux Application Production Grade

Denis Sicun
The Startup
Published in
10 min readOct 14, 2019
Just some developers trying to understand why their app keeps re-rendering — Photo by Mimi Thian on Unsplash

“They don’t teach you this in school…”

React is probably the most popular UI library for web development out there, and usually, my first choice when starting a new project. When your application gets a bit larger than a tutorial/code sample you are testing, you need a state management tool, and luckily we have plenty of tools for this task. One of the most popular choices would be Redux, and to complete this excellent stack, we have the library that makes this stack feel so natural React-Redux.

This stack became a popular choice for many projects for many reasons (well-maintained libraries, simple and readable code, reliable performance, and more). But this incredible stack has a pitfall that’s not covered well in tutorials or documentation. Unfortunately, it will become an issue when your application starts to grow, and performance starts to matter (just like any engineering problem). This issue is also a pretty tricky to discover if you don’t know where to look.

In this post, I’ll show you the exact issue, explain it, and show you how to solve it. Applying the concepts I’ll show you in this post will increase the performance of your application, which is crucial in production.

This post is not a tutorial on React or Redux. If you are entirely unfamiliar with React and Redux, I suggest you read the basics before moving on.

React official tutorial
Redux official tutorial

TL;DR

If you are using react-redux’s connect function poorly, redundant rendering of an entire component and it’s sub-components can occur on any state change in your app. This issue happens when you compute the new state in your mapStateToProps function. Since every processed action usually resolves into a completely new state tree (unless you return the same old state), the mapStateToProps method gets executed in all the components you used the connect function of react-redux. This behaviour is what makes react-redux update all components on state changes (elegant and straightforward).
For each component, the result of mapStateToProps is compared with the previous result and if the props changed, the component would re-render.

But, when we use some transformation on the state (like filtering data) in mapStateToProps, redundant render occurs, and that is a problem.

There are a few solutions to this problem:

1. Use connect with its 4th argument (which is called options) on each component. With this option, you need to override some react-redux’s internal compression functions.

2. Use selectors to memoize data passed to a component’s props in mapStateToProps. All you need to do is write functions to cache data passed to your component (it’s much easier then it sounds).

3. Use React’s lifecycle function shouldComponentUpdate to tell React if this component should re-render. In complex component trees, this can cause a fair share of bugs and nasty code. I use this approach only in rare edge cases.

4. If you are using React version 16.6 or higher, you can use the React.memo HOC (Higher Order Component) to cache the props of a function component (this doesn’t work yet for class components). This approach is very similar to using shouldComponentUpdate.

Out of these solutions, I find the use of selectors with the Reselect library the most efficient and most natural to code.

Now let’s Define the problem and solutions properly.

Basic Data Flow Of React-Redux

skip this part if you are sure how the data flow in react-redux works.

The moving pieces:

  1. Redux is our data store. It gives us a simple pattern to follow for creating our applications state tree and ways to update it.
  2. React is the library that allows us to create UI components.
  3. React-Redux a helper library that allows us to use the redux store in our react components.

The basic flow (mounting and updating phases of an application):

  1. We create an application store using Redux. This store is composed of reducers that should represent independent parts of your application.
  2. Some React components are ‘wired’ to the store to get data from it using react-redux’s connect function. This method passes the data to the component via its props.
  3. When the component mounts, it renders itself according to its props and state. A component can also pass its data to child components, which are part of it, and then they get rendered as well.
    This part ends the general mounting phase of an application.
  4. A Redux action is dispatched from a UI event or an external source (API callback, for example).
  5. A redux reducer processes this action and returns a reducer state.
  6. The data changes in the store, and the application is re-rendered accordingly. 4–6 are the primary update phases of the application.

The Problem

When an action is processed in a reducer, we want to change our application’s state. In Redux, state changes occur by your reducer, returning a new state object with the desired change. Meaning that every action returns a an entirely new state object.
When the new state is set in the store, any component connected to the store via the connect function from react-redux will have its mapStateToProps function executed to re-evaluate the effect of the state change on this component’s props. The mapStateToProps function is responsible for taking the needed data for a component from the application state and making it available in the component’s props.

Ultimately, if we have a component that derives it’s props from computed state, meaning we computed it in mapStateToProps (for example filtering date), the entire component re-renders. But why?

Both react-redux and React have a safety mechanism to prevent just this from happening. But unfortunately, both libraries implement it in a similar manner, resolving into this behaviour.
The new props are compared with the old props by a strict equality check (===). Since the state after the action is a new object, it is not === to the old state.

Here is a small example of comparing 2 objects in JS, which demonstrates that objects with the same data aren’t the same objects just like the newly created state.

When a React component receives new props, React tries to prevent the re-render of the component with a strict equality check of the props, and when that fails, the component and its whole subtree of components are re-rendered even if the data in the props stays the same!

If you think of a real production application, it has hundreds (if not thousands) of components in an application. You wouldn’t want the render method of some component called on every action any component can dispatch. That becomes a performance hit.

To demonstrate the problem I’ve created this small project on Github.
In this project, there are 2 separate components, a timer which increases its time every second and a message list, which filters a list of messages by a static search term.

If you start the project (it’s a simple Create-React-App project) and take a look at your browser’s console, you’ll see the render method of the messages list component called every second.
These 2 components cause that:

zztimer.js — dispatches an action to increase the time every second

The timer component is the engine of our redundant rendering. It changes the app’s state every second, and by that, it causes all the mapStateToProps functions to get called. But we don’t have a problem with it.

messages-list.js — a simple component to display some messages, after filtering them from the app state

The messages-list component is our actual problem. Whenever the mapStateToProps method of this component gets called, it will causes a re-rendered. That’s because we are filtering the state and creating a new messages array. The new message array makes the returned value of mapStateToProps to be a new props object, which is different from the old props object. Since the props objects aren’t equal strictly, the messages-list will re-renders.

Solving The Problem From Its Root

The solution to this is pretty elegant, selectors!

A selector is a cached (memoized if you want the formal language) result of a function that computes data. The arguments to create a selector are functions that tell it which data to cache, and a function to re-compute the data we need.

Selectors allow us to return new props from mapStateToProps only when the actual data needed to compute them changed — so no redundant rendering happens.

To create a selector, we can use the Reselect library (there are other libraries, but this one is my favorite).

It’s super easy to create a selector

// create a selector
const messagesListSelector = createSelector(
(messages) => messages.list,
(messages) => messages.searchAuthor,
(list, search) => {
return list.filter((message) => message.author === search)
}
);
// use a selector
function mapStateToProps(state){
const { messages } = state;

return {
messageList: messagesListSelector(messages),
}
}

So what’s going on here?

createSelector is imported from Reselect and it is used to create the selector.

In this case, we passed 3 arguments to the selector.
The first 2 arguments are functions to cache our input data for the computation we perform (filter a message list). The first function caches the list of messages, and the second caches the search term we filter by. The last argument of a selector is always the function that performs the computation itself.
The last function of the selector gets called, only if any of the previous functions got different data, meaning that the cached input data has changed.

We use the selector by passing it the data, which is relevant for caching, in this case, the messages part of the state.

Another way to use the selector is the following:

// create a selector
const messagesListSelector = createSelector(
(list, search) => list,
(list, search) => search,
(list, search) => {
return list.filter((message) => message.author === search)
}
);
// use a selector
function mapStateToProps(state){
const { messages } = state;

return {
messageList: messagesListSelector(messages.list, message.searchAuthor),
}
}

The result will be the same, but this little snippet demonstrates how you can pass different arguments to a selector.

The final result is that the redundant rendering has stopped in the messages-list component. Yay!

Now that data returned from mapStateToProps changes only if the relevant state has changed. And that stops the component’s re-render cycle at the very beginning.

There is no limit to how you can cache data with a Reselect. Since a selector is built from a list of functions, you can get full control of your data. And if that is not enough, you can also tell your selector how to compare data changes with the createSelectorCreator function from Reselect.

In the sample project I created, you can see the effect of the selector on the messages-list component on a different branch called with-selectors.
If you run it, you can see that the MessagesList component renders only once.

Selector Alternatives

So as I mentioned in the TL;DR section of this post, there are several ways to overcome the redundant rendering problem. Each of these solutions has its purpose, and they are tools that can be used for additional enhancements.

React - useMemo/React.memo
Starting from React version 16.6, we have hooks. The useMemo hook API was introduced with it, and basically, it memoizes props passed to a hook, in the way Reselect does. This functionality is great great for components that don’t connect to a Redux state. Also, this API is available for hooks only, so any class components you have needs to be convert to hooks (not always possible or worth the effort).

You can also use the React.memo HOC, which can be used for function components, in a similar manner to useMemo.

React-Redux - useSelector
From React-Redux version 7.1, the useSelector function was added for hooks. The name is a very misleading choice by react-redux. All this function does is to connect a hook to a redux store, just like connect + mapStateToProps. The function doesn’t memoize the data like a selector. So actually, this is more of a gotcha than an alternative to selectors.

React-Redux - connect function
There connect method of react-redux is a very sophisticated function. It is responsible for connecting our React component to the Redux store capabilities. Usually, all you pass connect only 2 arguments -mapStateToProps and mapDispatchToProps functions.
But did you know that it can accept 4 arguments?

The 3rd argument is a mergeProps function, which determines how your components final props look. This function is useful if your component derives props from a parent component and the store. With this function, you can determine how the props look like after mapStateToProps and mapDispatchToProps are processed.

But this is not related to the problem we are trying to solve.

The 4th argument is an options argument. It accepts a settings object which you can read about in the docs. The interesting options for our problem are pure, areStatePropsEqual, and areStatesEqual. You can override the areStatePropsEqual and areStatesEqual functions to use a deeper equality check to determine if the state coming to component has changed, and the props from mapStateToProps changed. By default, the equality check in areStatesEqual is strict equality (===), and it is the reason we get mapStateToProps called on every state change. areStatePropsEqual uses shallowEqual as a default equality check.
To use the areStatePropsEqual and areStatesEqual, the pure flag must be set to true (true by default).

The 4th argument of connect is very advanced, and when used wrong can also cause serious issues.

React - shouldComponentRender lifecycle function
This lifecycle method invokes after a react component receives props and state changes, and before it’s rendering. In this function, you can control if the component is re-rendered. With this lifecycle function, you can have the compression for props and state change, but this is not the optimal solution for our problem since we have already passed a big part of the components update cycle.

The only use I have found useful for this lifecycle in performance enhancements is always to return false for components that I don’t want ever to get re-rendered after they are mounted. There are very few cases you would use this so I wouldn’t count on it. Also, this makes your code look bad.

Conclusions

The use of selectors and Reselect solves the redundant rendering issues you can experience with react-redux. This solution is optimal for most use cases since it stops the props changes even to reach React, which is also a performance advantage.

Using Reselect is a significant improvement you can add to make your application at production-grade, but it’s not the only one. When you have a large scale production system, there are many things to consider.

I hope this post was helpful to you because there are more to come!

Thanks for bearing with me 😅

--

--

Denis Sicun
The Startup

RTC Team Leader @Kaltura | Not afraid of a long journey | 👨🏻‍💻 🏃🏻 🚴🏼‍♂️ 🏊🏼‍♂️