Understanding reselect and re-reselect

TL; DR

If you’re pretty comfortable with what Redux selection is and how it works, remember these tips:

Keep simple selectors simple

For simple use cases where we want to do a straight property lookup, all we need to do in mapStateToProps is drill down to the property we need. For example:

const mapStateToProps = (state, ownProps) => ({
    locationChanging: state.grid.locationChanging,
})

Property lookups on objects are fast. They allow us to select the data we care about, and they don’t require any further optimization.

If you have a unique ID, you probably should use re-reselect

When you have access to a unique ID that you’re using to calculate some data, or display a UI component in a list of items by ID, you would probably benefit from using re-reselect. This is because you have an easy way to get a dependable cache key that refers to the same entity over time.

For more details on why this is true, see the reselect docs.

You don’t have to use a memoized selector with redux-saga’s select

When writing sagas with redux-saga that interact with the Redux store, you will commonly use the select effect. It performs the same task as any other selector: targeting a specific piece of the Redux state that we want to get updates on. The select effect accepts a selector function and any number of optional (...args) (see the docs. Essentially the same ideas apply here as with mapStateToProps, except that the selector does not necessarily get ownProps as its second argument.

If you are doing a straight property lookup, you do not need to use a memoized selector. You can pass a regular function as the selector argument:

const isGridReady = yield select(state => state.grid.isGridReady)

In many cases, you don’t need anything more complex than this.


Defining ‘selection’

The core issue we’re dealing with is how to drill down to data that we need to get from the Redux state tree. Selection in this context is the act of targeting a specific piece of the Redux state that we want to get updates on. If our selector works correctly, the component that uses it will update when – and only when – there is a state update that it needs.

Connecting to the store with mapStateToProps

The connect function provided by react-redux gives us access to the Redux store with its first argument, mapStateToProps. mapStateToProps is a function that takes in two arguments, state and ownProps. state is the current state of the Redux store. ownProps are the props that are passed in to the instance of the component. mapStateToProps returns an object of props that is merged with the passed-in props to provide the final props object to the component.

Computing derived data with selectors

There are some cases when we need to do some computation to derive the data we need, based on what is in state and ownProps.

Consider this example:

const ChosenItemsList = props => {
    // ... more component code
};

const mapStateToProps = (state, ownProps) => {

    // `state.chosenItems` is a list of items that have been clicked on by the user.
    const { chosenItems } = state.chosenItems;

    // `itemType` is a given description, such as 'image' or 'zipFile'
    const { itemType } = ownProps;

    const chosenItemsByType =
        chosenItems.filter(chosenItem => chosenItem.type === itemType);

    return { chosenItemsByType };
};

export default connect(mapStateToProps)(ChosenItemsList);

The selection logic requires us to iterate through the state.chosenItems array. This is a potentially expensive (linear time complexity) operation when state.chosenItems contains a lot of elements.

Memoizing selection logic

Rather than putting that expensive array.filter() in mapStateToProps, we can use a selector to memoize the operation. Reselect gives us the createSelector function that does exactly this.

createSelector takes any number of function arguments. The first to n-1 functions are used as “input selectors”, and the nth function is turned into a memoized selector.
– input selectors do not transform the data they select; they merely select the inputs for the memoized selector.
– the memoized selector takes any number of selected inputs and computes the derived data.

const ChosenItemsList = props => {
    // ... more component code
};

const chosenItemsSelector = state => state.chosenItems;
const itemTypeSelector = (state, ownProps) => ownProps.itemType;

const chosenItemsByTypeSelector = createSelector(
    chosenItemsSelector,
    itemTypeSelector,
    (chosenItems, itemType) => {
        // This function will be memoized; it will only be called when the arguments change.
        // If the arguments have not changed, the result from the last time it was called will
        // be returned.
        const chosenItemsByType =
            chosenItems.filter(chosenItem => chosenItem.type === itemType);

        return { chosenItemsByType };
    });

const mapStateToProps = (state, ownProps) => ({
    chosenItemsByType: chosenItemsByTypeSelector(state, ownProps),
});

export default connect(mapStateToProps)(ChosenItemsList);

We’re able to avoid that expensive array iteration if our selector is used when neither chosenItems nor itemType have changed since the last time it was called.

Warnings about reselect

=== diffs

Reselect checks for changes using reference equality (===). This means that you have to respect the Redux expectation that your store’s properties are immutable. If your reducers modify some data within the state.items array without returning a new object reference, the selector will not detect a new argument, and you will receive a cached item when you expect to receive the newest one.

Cache size of 1

Per the reselect docs:

Selectors created with createSelector have a cache size of 1. This means they always recalculate when the value of an input-selector changes, as a selector only stores the preceding value of each input-selector.

This means that your expensive calculations will always run when an input changes. This applies across all call sites for a given selector. Let’s assume you have two instances of <ChosenItemsList>, each with different itemType props.

<div>
    <MainContent>
        <ChosenItemsList itemType={'image'} />
    </MainContent>
    <Sidebar>
        <ChosenItemsList itemType={'zipFile'} />
    </Sidebar>
</div>

Let’s assume that <MainContent> and <Sidebar> are receiving props updates at different points in time, causing us to alternate between rendering the <ChosenItemsList> components. If this happens, the memoized function will always run, because it is receiving a different itemType argument. This is explained in the reselect docs.

Using re-reselect

How can we avoid this? By changing our caching logic. Re-reselect give us the ability to set up more than one cache for our selector. Its main export is a function called createCachedSelector, which is actually a wrapper around createSelector from reselect. It creates a separate cache for each cache key you create. The first portion of the function signature for createCachedSelector is the same as for createSelector. However, it adds a final argument: a keySelector that returns the cache key.

The keySelector function receives the same arguments as the final memoized selector – in the example below, it gets chosenItems and itemType.

const ChosenItemsList = props => {
    // ... more component code
};

// `state.chosenItems` is a list of items that have been clicked on by the user.
const chosenItemsSelector = state => state.chosenItems;

// `itemType` is a given description, such as 'image' or 'zipFile'
const itemTypeSelector = (state, ownProps) => ownProps.itemType;

const chosenItemsByTypeSelector = createCachedSelector(
    chosenItemsSelector,
    itemTypeSelector,
    (chosenItems, itemType) => {
        // A copy of this selector function will be memoized for each different `itemType`.
        const chosenItemsByType =
            chosenItems.filter(chosenItem => chosenItem.type === itemType);

        return { chosenItemsByType };
    })
    (
        // This is the `keySelector` function. It returns a number or string that is used as the
        // cache key for each copy of the memoized selector function.
        (chosenItems, itemType) => itemType;
    );

const mapStateToProps = (state, ownProps) => ({
    chosenItemsByType: chosenItemsByTypeSelector(state, ownProps),
});

export default connect(mapStateToProps)(ChosenItemsList);

This means that all instances of <ChosenItemsList> that have the same itemType will share the same selector cache. In the situation above where we have two <ChosenItemsList>s with different itemType props, we can alternate between rendering the components and be sure that the memoized selector will only run when chosenItems has changed since the last selector call.

Using re-re-reselect

And if you’ve made it this far, you might just want to check out re-re-reselect. Or not, your choice 🙂

Testing state mutations with Enzyme.js and wrapper.update()

When testing React components, I often reach for Enzyme.js, a library of testing utilities for React components developed by the team at Airbnb. It’s become a standard tool among React developers. Recently, I was testing a component that used state to determine the className given to a child component. Here’s the general idea in code:

import React from 'react';

export default class FormWithCollapsableRows extends Component {

    constructor(props: Props) {
        this.state = {
            hidden: false,
        };
    }

    toggleHidden() {

        /**
         * Toggles the state for form rows.
         */

        this.setState(prevState => ({
            hidden: !prevState.hidden,
        }));
    }

    setFormRowClass(rowType: string) {

        /**
         * Sets the class names for each form row, based on the
         * row type and whether `state.hidden` is true or false.
         */

        const {
            hidden,
        } = this.state;
        const partyRowClassNames = hidden ? ['hidden'] : [];

        return partyRowClassNames.concat([rowType]).join(' ');
    }

    render() {

        return <tbody className='expandable-table'>

            <tr className={this.setFormRowClass('row-type-0')}>
                <td>
                    Row 0 content goes here
                </td>
            </tr>

            <tr className={this.setFormRowClass('row-type-1')}>
                <td>
                    Row 1 content goes here
                </td>
            </tr>

        </tbody>;
    }
}

In the LESS style sheet, I have something like this going on:

.expandable-table {
    tr {
        .hidden {
            visibility: collapse;
        }
    }
}

When the component’s has state.expanded === false, the hidden class is applied to the rows, hiding them with the style visibility: collapse. I wanted to test whether the rendered component was correctly applying my class naming logic, so I wrote up a test like this:

import { shallow } from 'enzyme';
import test from 'tape';

import FormWithCollapsableRows from './FormWithCollapsableRows';

test('When `state.hidden === false`:', t => {

    const wrapper = shallow(<FormWithCollapsableRows />);

    // The component mounts with `state.hidden ==== false`,
    // so the class names should not include "hidden" to
    // begin with.

    t.notOk(
        wrapper.containsMatchingElement(
            <tr className='hidden row-type-0' />),
        'the rows should not be hidden');

    t.end();
});

test('When `state.hidden === true`:', t => {

    const wrapper = shallow(<FormWithCollapsableRows />);

    // Once we toggle `state.hidden` to `true`, the class
    // names should include "hidden"

    wrapper.instance().toggleHidden();

    t.ok(
        wrapper.containsMatchingElement(
            <tr className='hidden row-type-0' />),
        'the rows should be hidden');

    t.end();
});

However, I found that my second test case was failing. After I made the call to wrapper.instance().toggleHidden(), I expected the class name to update according to my render function, which called this.setFormRowClass(). Instead, it seemed that the wrapper I was testing had not received the state update.

It took some perusal of the Enzyme docs to find update(), a method on shallow wrappers that allows you to force a re-render on the component. This turned out to be exactly what I needed. I modified the second test case like this:

test('When `state.hidden === true`:', t => {

    const wrapper = shallow(<FormWithCollapsableRows />);

    // Once we toggle `state.hidden` to `true`, the class
    // names should include "hidden"

    wrapper.instance().toggleHidden();

    // Triggers a re-render, so we know that `this.setFormRowClass` 
    // will be called:

    wrapper.update();

    t.ok(
        wrapper.containsMatchingElement(
            <tr className='hidden row-type-0' />),
        'the rows should be hidden');

    t.end();
});

Now the test passes, as expected.

Using this.setState() in React

Interactive forms

I was recently working on a form in React that looked something like this:

Shipping Address:
User Name
123 My Street
This Town, USA 45678

[ ] Use shipping address as billing address.

Billing Address:
User Name
456 Other Ave
That City, USA 56789

I wanted the form to have the following traits:

  • If the Use shipping address as billing address box is checked, copy over the data from the shipping address to the billing address
  • If there was previously data in the billing address and the user un-checks the box, bring back the old billing address

To accomplish these goals, I needed to implement some state management by using a class component and this.state. This is idiomatic React; once you understand how this.state works, you’d probably see this problem as trivial.

First approach

Here’s how you might instinctively use this.setState() in a class method on your component:

export default class OrderForm extends Component {

    constructor(props) {
        super();

        this.state = {
            selectedShippingAddress: null,
            savedBillingAddress: null,
            selectedBillingAddress: null,
            useShippingAsBilling: false,
        };

        this.handleToggleUseShippingAsBilling.bind(this);
    }

    // Version 1

    handleToggleUseShippingAsBilling() {

        const {
            useShippingAsBilling,
            selectedShippingAddress,
            savedBillingAddress,
        } = this.state;

        // If useShippingAsBilling is currently false, set it to `true`
        // and copy `selectedShippingAddress` to `selectedBillingAddress`

        if (!useShippingAsBilling) {
            return this.setState({
                useShippingAsBilling: true,
                selectedBillingAddress: selectedShippingAddress,
            });
        }

        // If useShippingAsBilling is currently true, set it to `false` 
        // and reset `selectedBillingAddress` to the `savedBillingAddress`

        return this.setState({
            useShippingAsBilling: false,
            selectedBillingAddress: savedBillingAddress,
        });
    }

    render() {
        // ... return JSX markup here
    }

As I found out by experience, when saving properties on this.state via this.setState(), React sometimes delays the operation:

Think of setState() as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately.

This makes things a bit tricky. The handleToggleUseShippingAsBilling() method in Version 1 first reads the current value of this.state in order to determine what should happen next. If that happens before React has completed a previous this.setState() call, then we will end up with an undesired program state. We need to know that we are starting from the version of state that directly precedes the new state. Fortunately, there is a usage pattern for this.setState() that does exactly that. This is pretty clearly described in the React docs, but I always find that a concrete example helps me remember how things work.

Two kinds of this.setState()

You can use this.setState() in two ways:

  1. If you give it an object, it will overwrite this.state with that object’s properties. Let’s imagine we want to toggle a current state value called useShippingAsBilling. To use this form of this.setState(), we would read the value first, and then use this.setState to write the new value.
    const {
        useShippingAsBilling: prevValue, 
    } = this.state;
    
    this.setState({
        useShippingAsBilling: !prevValue;
    });
    
  2. If you give it a function, it will invoke the function and use its return value to overwrite this.state. The function signature will accept two arguments:
    1. The previous state value, or prevState
    2. The current props

These arguments are ensured by React to be up to date when the function is run. We won’t be using props here, only the prevState. Here’s how it looks with this form of this.setState():

this.setState(prevState => ({
    useShippingAsBilling: !prevState.useShippingAsBilling,
});

A better way

To round out the example, here’s how I used the second form of this.setState to solve this particular problem.

{
    // ... same as above

    // Version 2
    // Reads the version of this.state that we want.

    handleToggleUseShippingAsBilling() {

        return this.setState(prevState => {

            // If useShippingAsBilling is currently false, set it to `true` 
            // and copy `selectedShippingAddress` to `selectedBillingAddress`

            if (!prevState.useShippingAsBilling) {
                return {
                    useShippingAsBilling: true,
                    selectedBillingAddress: prevState.selectedShippingAddress,
                };
            }

            // If useShippingAsBilling is currently true, set it to `false` 
            // and reset `selectedBillingAddress` to `savedBillingAddress`

            return {
                useShippingAsBilling: false,
                selectedBillingAddress: prevState.savedBillingAddress,
            };
        });
    }

    render() {
        // ... return JSX markup here
    }
}

By using the right form of this.setState(), we can reliably read the prevState value and use that to compute the next state value. Success!