Chong Lip Phang
Chong Lip Phang

Reputation: 9279

How to memoize useSelector() with createSelector()?

I wish to explain the memoization on a Redux store on my programming website.

What I have now on CodeSandbox:

import { useCallback, useMemo, memo } from "react";
import { useSelector, useDispatch } from "react-redux";
import { createSelector } from "reselect";

const Plus = memo(({ onIncrement }) => {
  console.log("rendering <Plus>...");
  return <button onClick={onIncrement}>+</button>;
});

export default () => {
  console.log("rendering <App>...");
  const v = useSelector((state) => {
    console.log("useSelector() invoked");
    return state;
  });
  const dispatch = useDispatch();
  const increment = useCallback(() => dispatch({ type: "INCREMENT" }), [
    dispatch
  ]);
  return (
    <div>
      <span>{v}</span>
      <Plus onIncrement={increment} />
    </div>
  );
};

As you can see, I have successfully memorized the dispatch() function with useCallback(). However, every time the button is clicked, useSelector() is called twice.

Is there a way to call it once only? I am thinking about memorization with createSelector() from the 'reselect' library. I don't understand that library. Could someone provide some guidance?

References:

  1. Using memorizing selectors 2) The 'reselect' library

Upvotes: 0

Views: 2704

Answers (1)

hungdoansy
hungdoansy

Reputation: 486

They are working correctly by having running the selectors 2 times. It would be easier and more obvious if you also print out the state (the counter) inside the selector.

Reference: https://react-redux.js.org/api/hooks#equality-comparisons-and-updates

Things will be this way:

  1. A button and 0 are rendered on the UI.
  2. You click the button, which dispatches an action.
  3. The selector runs for the first time as it sees an action dispatched, it detects a change in the returned value, so it forces the component to re-render. Log is printed out with the NEW value (not the OLD one).

However, when an action is dispatched to the Redux store, useSelector() only forces a re-render if the selector result appears to be different than the last result. As of v7.1.0-alpha.5, the default comparison is a strict === reference comparison. This is different than connect(), which uses shallow equality checks on the results of mapState calls to determine if re-rendering is needed. This has several implications on how you should use useSelector()

  1. The component re-renders, which triggers the the second log ("rendering ...").
  2. That also leads to a re-run of the selector, which explains the third log.

When the function component renders, the provided selector function will be called and its result will be returned from the useSelector() hook. (A cached result may be returned by the hook without re-running the selector if it's the same function reference as on a previous render of the component.)

Upvotes: 2

Related Questions