React Redux Performance Optimization - Selectors & Reselect
React Redux Performance Optimization - Selectors & Reselect

In this video you will learn about selectors in Redux and how to improve their performance by using Reselect library.
Let's jump right into it.

Here I have a generated create-react-app project with redux-connect. As you can see here we have 2 reducers and we have 2 actions: change username and add user. When we type something in input we change the username field in reducer and when we click on Add button we save user in our reducer.

It may seem as a lot of already written code so if some parts are not clear for you I will link my previous video about reducers here on the top.


Now let's talk about selectors. So as you can see normally we just write in mapStateToProps fields that we need from state. This is completely fine but just imagine that we have several components where we connect to the same data. First of all it's code duplicate and secondly it's easier to make an error because we don't use a single function but we each time write the full path.

To avoid this we can use selectors. What is selector? It's just a function which returns the part of the state. Let's create a selectors.js file in store and a selector to get users array from our state.

src/store/selectors.js

export const usersSelector = (state) => state.users.users;

As you can see there is no magic here. It's just a function which gets the global state and returns some part of it. The important thing is a codestyle. Normally we have a postfix selector in the name of we start the word with selectSomething.

Now we can use this selector in our App component.

import { usersSelector } from "./store/selectors";

const mapStateToProps = (state) => {
  return {
    users: usersSelector(state)
  };
};

So we just moved getting of some properties to selector and it works exactly like before but we can now reuse the same function everywhere.

Let's try a more complex example. For example we want to have a search input and get a filtered array of all users that we have.

App.js

handleSearch = (e) => {
  this.props.dispatch({ type: "CHANGE_SEARCH", payload: e.target.value });
};


<input
  type="text"
  placeholder="Search"
  value={this.props.search}
  onChange={this.handleSearch}
/>

We also need to add this field in our reducer.

src/store/reducers/users.js

const initialState = {
  users: [],
  username: "",
  search: "",
};

const reducer = (state = initialState, action) => {
  switch (action.type) {
    ...
    case "CHANGE_SEARCH":
      return {
        ...state,
        search: action.payload,
      };
    default:
      return state;
  }
};

As you can see now our search property changes when we type in our input. So we have in reducer a searched string and our users. Which means we can filter users on the fly inside mapStateToProps.

const filteredUsers = state.users.users.filter((user) => {
  console.log("filtering users...");
  return user.includes(state.users.search);
});

return {
  users: usersSelector(state),
  filteredUsers: filteredUsers
}

As you can see everything is working and every time when we type we get a filtered users array.


But there is a super important thing to remember. Every time when our Redux state changes all mapStateToProps are being called. Doesn't matter on what properties you are subscribed. They are all being called. This is why with each letter that we type we see console.log of calling mapStateToProps.

Which is actually fine if we just select properties from the object. Because if this properties didn't change React won't rerender the component. But it's not find if we make some additional calculation. Because in our case now our filtering users is being called every single time when we change the state. This is really bad because if we have lots of data it will take a lot of time.

But we have a solution for that. What we want to do is define when we must recalculate our filtered users. For this we must install an additional library which does exactly that and it is the recommended solution from Redux.

yarn add reselect

Now let's move our filtering in selector. First of all because it make our code cleaner and secondly it will be easier to use reselect there.

selectors.js

export const filteredUsersSelector = (state) => {
  return state.users.users.filter((user) => {
    console.log("filtering users...");
    return user.includes(state.users.search);
  });
};

So everything is working as before but at least our mapStateToProps is clean.


Now the question how reselect helps us to make filter less often? It memoizes value. Memoization means that the calculation of our filter is being cached until some of the properties that we define won't change. In our case to calculate filtered users we need users property from the state and search property. So if this 2 properties didn't change then we will get a cached value back.

import { createSelector } from "reselect";

export const filteredUsersSelector = createSelector(
  (state) => state.users.users,
  (state) => state.users.search,
  (users, search) => {
    return users.filter((user) => {
      console.log("filtering users...");
      return user.includes(search);
    });
  }
);

So here we first define as a functions all dependencies of our calculations. Then in the last function we make calculations based on all properties that we defined before. The syntax may look scary but it's just 1 or several functions which define our dependencies and then the last function with calculations.

Now as you can see in browser our filtering is not called when we change username in state. So any changes in state except of this 2 properties that we defined are completely ignored. Only when users array or search changes, we recalculate our data.

But now I think you want to ask why we are not memoizing every selector that we have? Because it also takes performance to store a value and check if dependencies changed and it doesn't make any sense to make it for simple selections on the state.

So here are important points to remember.

  • Defining selectors is good because they are reusable and our mapStateToProps looks cleaner and we can easily test our selectors
  • Memoization without additional library is not possible but extremely needed when you have calculations that you want to make
  • Reselect library is awesome to solve exactly this problems

Also if you want to improve your programming skill I have lots of full courses regarding different web technologies.

📚 Source code of what we've done