Search code examples
reduxreact-reduxredux-toolkitmemoizationusecallback

How to make memoization work with useCallback() and Redux-Toolkit createSelector()?


According to the documentation here:

Creating Unique Selector Instances:

There are many cases where a selector function needs to be reused across multiple components. If the components will all be calling the selector with different arguments, it will break memoization - the selector never sees the same arguments multiple times in a row, and thus can never return a cached value.

The standard approach here is to create a unique instance of a memoized selector in the component, and then use that with useSelector. That allows each component to consistently pass the same arguments to its own selector instance, and that selector can correctly memoize the results.

For function components, this is normally done with useMemo or useCallback:

When I did the following on codesandbox:

import { useCallback } from "react";
import { useSelector, useDispatch } from "react-redux";
import { increment, selectSum } from "./reducer";

function Sum({ value }) {
  const selectSumMemoized = useCallback(selectSum, []);
  const sum = useSelector((state) => selectSumMemoized(state, value));
  return (
    <div>
      sum: {sum} (10+100+{value})
    </div>
  );
}

export default () => {
  const dispatch = useDispatch();
  return (
    <div>
      <Sum value={1} />
      <Sum value={2} />
      <button onClick={() => dispatch(increment())}>
        change state and re-render
      </button>
    </div>
  );
};

as I initiated a re-render by clicking the button repeatedly, the output selector of selectSum logged twice, implying that memoization is not working across multiple components.

What changes do I need to make for it to work correctly?


Solution

  • I figured it out.

    Basically, I need to define and use the following function (just add ()=>):

    const makeSelectSum = () =>
      createSelector([select100, select10, (state, n) => n], (a, b, num) => {
        console.log("output selector called: " + num);
        return a + b + num;
      });
    

    Then:

    const selectSumMemo = useMemo(makeSelectSum, []);
    const sum = useSelector((state) => selectSumMemo(state, value));
    

    or:

    const selectSumMemo = useCallback(makeSelectSum(), []);
    const sum = useSelector((state) => selectSumMemo(state, value));
    

    instead of using this one directly:

    const selectSum =
      createSelector([select100, select10, (state, n) => n], (a, b, num) => {
        console.log("output selector called: " + num);
        return a + b + num;
      });