Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Trouble with simple example of React Hooks useCallback

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

like image 219
Peter Kellner Avatar asked Feb 28 '19 19:02

Peter Kellner


People also ask

Are there any problems using useCallback?

Problem. One reason to use useCallback is to prevent a component from re-rendering unless its props have changed. In this example, you might think that the Todos component will not re-render unless the todos change: This is a similar example to the one in the React.

For what purpose is the useCallback () hook used?

The useCallback hook is used when you have a component in which the child is rerendering again and again without need. Pass an inline callback and an array of dependencies. useCallback will return a memoized version of the callback that only changes if one of the dependencies has changed.

Does useCallback cause Rerender?

The key takeaway here is that useCallback returns you a new version of your function only when its dependencies change, saving your child components from automatically re-rendering every time the parent renders.

Why is Forstate not updating immediately?

The answer: They're just queues setState , and React. useState create queues for React core to update the state object of a React component. So the process to update React state is asynchronous for performance reasons. That's why changes don't feel immediate.


1 Answers

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

like image 178
Ryan Cogswell Avatar answered Oct 21 '22 19:10

Ryan Cogswell