Performant Redux Selectors with Reselect

In Redux a selector is a piece of logic that gets a specific piece of state from the store. Additionally, a selector can compute data from a given state, allowing the store to keep only basic raw data. Selectors are usually used as part of the binding between the store and the container components. In React for example, the mapStateToProps function that’s passed to React Redux’s connect function is where selectors would be used to select the needed slices of state for a container component.

Selectors can be created without the help of a library like Reselect, but using Reselect carries a few benefits in terms of developer experience and performance:

  • Selectors created using Reselect’s createSelector function are memoized. That’s a fancy word to mean that the function remembers the arguments passed-in the last time it was invoked and doesn’t recalculate if the arguments are the same. You can view it a little bit like caching.
  • Reselect selectors can be composed/chained together easily. This way, each selector stays small and focused on one task.

Installation

Just add the reselect package to your project using npm or Yarn:

$ npm install reselect

# or, using Yarn:
$ yarn add reselect

With this, you can start creating selectors either in the same file as your reducers, or in their own separate file. Here for example we create some selectors for a simplistic todo app where we’ll like to select todo items that contain the string “milk” and those that also contain the string “bread”:

todo.reducer.js (partial)

// ...
import { createSelector } from 'reselect';

const todoSelector = state => state.todo.todo;

export const todosWithMilk = createSelector([todoSelector], todos => {
  return todos.filter(todo => todo.title.toLowerCase().includes('milk'));
});

export const todosWithMilkAndBread = createSelector([todosWithMilk], todos => {
  return todos.filter(todo => todo.title.toLowerCase().includes('bread'));
});

// ...

With this example, we have 3 selectors: todoSelector, todosWithMilk and todosWithMilkAndBread. The first selector is known as an input selector. Input selectors are as simple as it gets, they take in the state as an argument and return a slice of that state. Input selectors should always be pure functions.

The 2nd and 3rd selectors in our example, todosWithMilk and todosWithMilkAndBread, are selectors created using Reselect’s createSelector function. createSelector expects 2 arguments: an array of input selector(s) as the 1st argument and a function as the 2nd argument, known as the transform function. The transform function receives the result(s) from the input selector(s) as arguments and should return the desired computed piece of state.

Selector composition is also demonstrated here, with the todosWithMilkAndBread being composed on top of the todosWithMilk selector. This helps keep your selector logic simple.

Note that you can also pass-in the input selectors to createSelector as a list of arguments, no need for them to be in an array. As long as the last argument passed-in is the transform function.

Using our selectors

Let’s make use of our todosWithMilk and todosWithMilkAndBread selectors in a Todos container component:

containers/Todos.js

import React from 'react';
import { connect } from 'react-redux';

import { deleteTodo } from '../actions';
import { todosWithMilk, todosWithMilkAndBread } from '../reducers';

function Todos({
  withMilk,
  withMilkAndBread,
  error,
  loading
}) {
  return (
    <div>
      {/* ... */}
    </div>
  );
}

const mapStateToProps = state => {
  return {
    withMilk: todosWithMilk(state),
    withMilkAndBread: todosWithMilkAndBread(state),
    error: state.todo.error,
    loading: state.todo.loading
  };
};

const mapDispatchToProps = dispatch => {
  return {
    onDelete: id => {
      dispatch(deleteTodo(id));
    }
  };
};

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Todos);

As you can see, it’s as easy as calling our selectors with the state object passed-in. Behind the scenes our selectors will verify if it was called with the same argument values the last time it ran, and it won’t recompute the state if that’s the case.

More Realistic Example

Our previous example is rather simplistic and in a real app you’ll instead most often have multiple input selectors and compute the desired slice of state based on the result of these input selectors. For example, in a more realistic todo app, you could have an input field to filter down the visible todo items that contain a specified search term. This could be accomplished with selectors that look a little bit like the following:

todo.reducer.js (partial)

// ...
import { createSelector } from 'reselect';

const todoSelector = state => state.todo.todos;
const searchTermSelector = state => state.todo.searchTerm;

export const filteredTodos = createSelector(
  [todoSelector, searchTermSelector],
  (todos, searchTerm) => {
    return todos.filter(todo => todo.title.match(new RegExp(searchTerm, 'i')));
  }
);

// ...

And with this, we can use the filteredTodos selectors to get all the todos if there’s no searchTerm set in the state, or a filtered list otherwise.

👷‍ I hope that with this you'll feel comfortable to start using selectors as part of your Redux apps and reap the performance benefits. You can refer to the project's readme for a more in-depth look at the API and to learn about more advanced use cases like accessing component props in selectors.

  Tweet It

🕵 Search Results

🔎 Searching...

Sponsored by #native_company# — Learn More
#native_title# #native_desc#
#native_cta#