reselect memoization ignore irrelevant updates

982 views Asked by At

Following the example on the reselect docs:

import { createSelector } from 'reselect'

const shopItemsSelector = state => state.shop.items

const subtotalSelector = createSelector(
  shopItemsSelector,
  items => items.reduce((acc, item) => acc + item.value, 0)
)

In a typical redux app, subtotalSelector will recompute if a user updates item.name, even though this has no impact on the result. Is there a way to avoid this?

2

There are 2 answers

1
Stan Luo On

Two solutions:

  1. Let it be. Unless you have a large number of items, the browser's computing capacity is well enough to handle the recompute.

  2. Seperate prices from the item object. That is, you have state.shop.items.itemNames(containing id-name pairs) and state.shop.items.itemValues(containing id-value pairs). Then only the itemValues is passed to the selector.

1
Michael Pearson On

I have a similar problem and I have found a sort of hack to get arround it.

I have a complex set of filters, and a huge number of items to filter through. Part of the filter state includes display state. I want to ignore changes in the display state so I don't filter a huge list all the time. This is an easy-ish solution:

const getFilters = createSelector(
    state => state.filters,
    filters => {
        const filtersWithoutDisplay = {};
        const ignoreObj = { collapsed: null };
        for (let filterGroup in filters) {
            filtersWithoutDisplay[filterGroup] = Object.assign({}, filters[filterGroup], ignoreObj);
        }
        // We create a new object every time, so this cannot be memoized properly unless we stringify.
        return JSON.stringify(filtersWithoutDisplay);
    }
);

It returns a JSON string that has to be parsed, but it's a primitive so as an input to another selector it doesn't trigger a recomputation if the actual contents don't change. That's kind of a hack.

You could also define an object outside of the selector function and always keep the same reference, change the insides according to this same patter, and then use a custom deep equality check by pulling in createSelectorCreator, as explained here https://github.com/reactjs/reselect#q-why-is-my-selector-recomputing-when-the-input-state-stays-the-same . This is probably a better way to go, but as it says:

Always check that the cost of an alternative equalityCheck function or deep equality check in the state update function is not greater than the cost of recomputing every time.

That goes for the JSON.stringify hack as well. I wouldn't do it for the huge list, but for the filters, sure.

In my situation, it's probably better to refactor my state because the filter values may be a separate concern from the filter display settings, and this may not be the only time I want them separate.