Tutorial

Performant Redux Selectors with Reselect

Published on July 15, 2018
    Default avatar

    By Alligator.io

    Performant Redux Selectors with Reselect

    While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the ā€œreport an issueā€œ button at the bottom of the tutorial.

    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.

    Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

    Learn more about us


    About the authors
    Default avatar
    Alligator.io

    author

    Still looking for an answer?

    Ask a questionSearch for more help

    Was this helpful?
    Ā 
    Leave a comment
    ļ»æ

    This textbox defaults to using Markdown to format your answer.

    You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

    Try DigitalOcean for free

    Click below to sign up and get $200 of credit to try our products over 60 days!

    Sign up

    Join the Tech Talk
    Success! Thank you! Please check your email for further details.

    Please complete your information!

    Get our biweekly newsletter

    Sign up for Infrastructure as a Newsletter.

    Hollie's Hub for Good

    Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

    Become a contributor

    Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

    Welcome to the developer cloud

    DigitalOcean makes it simple to launch in the cloud and scale up as you grow ā€” whether you're running one virtual machine or ten thousand.

    Learn more
    DigitalOcean Cloud Control Panel