14

I'm trying to make a simple example that follows the React Hooks example in the doc at page https://reactjs.org/docs/hooks-reference.html#usecallback

Without useCallback the code works find as is in this example:

import React, { useCallback } from "react";

function Test(props) {
  function doSomething(a, b) {
    console.log("doSomething called");
    return a + b;
  }

  return (
    <div>
      {Array.from({ length: 3 }).map(() => (
        <div>{doSomething('aaa','bbb')}</div>
      ))}
    </div>
  );
}

export default Test;

However, when I add what I think is the right code for useCallback as follows, I get an error (a is not defined)

import React, { useCallback } from "react";

function Test(props) {
  function doSomething(a, b) {
    console.log("doSomething called");
    return a + b;
  }

  const memoizedCallback = useCallback(
    () => {
      doSomething(a, b);
    },
    [a, b]
  );

  return (
    <div>
      {Array.from({ length: 3 }).map(() => (
        <div>{memoizedCallback("aaa", "bbb")}</div>
      ))}
    </div>
  );
}

export default Test;

The problem code is here:

https://stackblitz.com/edit/react-usememo2?file=Hello.js

1 Answer 1

20

The intent of useCallback is to be able to leverage props or state that are in the current scope and that may change on re-render. The dependency-array then tells React when you need a new version of the callback. If you are trying to memoize an expensive computation, you need to use useMemo instead.

The example below demonstrates the differences between useCallback and useMemo and the consequences of not using them. In this example I am using React.memo to prevent Child from re-rendering unless its props or state change. This allows seeing the benefits of useCallback. Now if Child receives a new onClick prop it will cause a re-render.

Child 1 is receiving a non-memoized onClick callback, so whenever the parent component re-renders, Child 1 always receives a new onClick function so it is forced to re-render.

Child 2 is using a memoized onClick callback returned from useCallback and Child 3 is using the equivalent via useMemo to demonstrate the meaning of

useCallback(fn, inputs) is equivalent to useMemo(() => fn, inputs)

For Child 2 and 3 the callback still gets executed every time you click on Child 2 or 3, useCallback just ensures that the same version of the onClick function is passed when the dependencies haven't changed.

The following part of the display helps point out what is happening:

nonMemoizedCallback === memoizedCallback: false|true

Separately, I'm displaying somethingExpensiveBasedOnA and a memoized version using useMemo. For demonstration purposes I'm using an incorrect dependency array (I intentionally left out b) so that you can see that the memoized version doesn't change when b changes, but it does change when a changes. The non-memoized version changes whenever a or b change.

import ReactDOM from "react-dom";

import React, {
  useRef,
  useMemo,
  useEffect,
  useState,
  useCallback
} from "react";

const Child = React.memo(({ onClick, suffix }) => {
  const numRendersRef = useRef(1);
  useEffect(() => {
    numRendersRef.current++;
  });

  return (
    <div onClick={() => onClick(suffix)}>
      Click Me to log a and {suffix} and change b. Number of Renders:{" "}
      {numRendersRef.current}
    </div>
  );
});
function App(props) {
  const [a, setA] = useState("aaa");
  const [b, setB] = useState("bbb");

  const computeSomethingExpensiveBasedOnA = () => {
    console.log("recomputing expensive thing", a);
    return a + b;
  };
  const somethingExpensiveBasedOnA = computeSomethingExpensiveBasedOnA();
  const memoizedSomethingExpensiveBasedOnA = useMemo(
    () => computeSomethingExpensiveBasedOnA(),
    [a]
  );
  const nonMemoizedCallback = suffix => {
    console.log(a + suffix);
    setB(prev => prev + "b");
  };
  const memoizedCallback = useCallback(nonMemoizedCallback, [a]);
  const memoizedCallbackUsingMemo = useMemo(() => nonMemoizedCallback, [a]);
  return (
    <div>
      A: {a}
      <br />
      B: {b}
      <br />
      nonMemoizedCallback === memoizedCallback:{" "}
      {String(nonMemoizedCallback === memoizedCallback)}
      <br />
      somethingExpensiveBasedOnA: {somethingExpensiveBasedOnA}
      <br />
      memoizedSomethingExpensiveBasedOnA: {memoizedSomethingExpensiveBasedOnA}
      <br />
      <br />
      <div onClick={() => setA(a + "a")}>Click Me to change a</div>
      <br />
      <Child onClick={nonMemoizedCallback} suffix="1" />
      <Child onClick={memoizedCallback} suffix="2" />
      <Child onClick={memoizedCallbackUsingMemo} suffix="3" />
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Edit useCallback and useMemo

Here's a related answer: React Hooks useCallback causes child to re-render

Sign up to request clarification or add additional context in comments.

4 Comments

On the first example, it still outputs the console.log 3 times. What am I not getting? (hard question I guess)
You're just misunderstanding the purpose of useCallback. useCallback does not memoize the results of the function -- it memoizes the function itself. In the first example there is no good reason to use it, but in the second example it would prevent the onClick property of Child from changing on re-renders. If you want to memoize the results (to prevent redundant executions of an expensive function), you should be be using useMemo.
you are so right I don't get it. I'm stymied by this comment in the docs: useCallback(fn, inputs) is equivalent to useMemo(() => fn, inputs). I'm putting courseware together and am a little lost, looking for good examples.
I've replaced my example with one that is no longer based on your code, but that should help clear things up if you spend enough time studying it, interacting with it, and viewing the results.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.