redux Memoizing derived data


Example

In your redux store you hold the raw data. Some times the raw data is all you need, but other times you need to derive new data from the raw data, often by combining parts of the raw data.

A common use case for deriving data is filtering a list of data based on a criteria, where both the list and the criteria may be changed.

The following example implements mapStateToProps and filters a list of strings to keep those who match a search string, producing a new prop filteredStringList that can be rendered by a React Component.

// Example without memoized selectors
const mapStateToProps(state) => {
    const {stringList, searchString} = state;

    return {
        filteredStringList: stringList
            .filter(string => string.indexOf(searchString) > -1)
    };
}

To keep the reducers simple you should only hold the list of data and the filtering criteria in the store, which means you need to derive the data on read time (like we did in the example above).

Deriving data on read time poses two problems:

  1. If the same data is derived many times it could compromize performance.
  2. If the same data is needed in different components, you may find that you are duplicating the code to derive data.

The solution is to use memoized selectors that are defined only once. The React community suggests using the npm library reselect to create memoized selectors. In the example below we achieve the same result as in the first example, only with memoized selectors.

// Create memoized selector for filteredStringList
import {createSelector} from 'reselect';

const getStringList = state => state.stringList;

const getSearchString = state => state.searchString;

const getFilteredStringList = createSelector(
    getStringList,
    getSearchString,
    (stringList, searchString) => stringList
        .filter(string => string.indexOf(searchString) > -1)
);

// Use the memoized selector in mapStateToProps
const mapStateToProps(state) => {
    return {
        filteredStringList: getStringList(state)
    };
}

Note that the two first selectors getStringList and getSearchString are not memoized, because they are so simple that it would provide no performance gain. They still need to be created because we need to pass them as dependencies to createSelector, so that it knows when to reuse the memoized result and when to compute a new result.

The memoized selector will use the function passed as the last argument passed to createSelector to compute the derived data (in our example the function that returns the filtered string list). Each time the memoized selector is called, if the dependencies are not changed since the last time it was called, (in our example stringList and searchString), the memoized selector will return the previous result, saving the time it would take recompute it.

You can think of selectors (memoized or not memoized) as getters for the store state, just like action-creators are setters.

You can find more examples on computing derived data in the Redux documentation's recipes section.