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
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.
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.
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.
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.
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);
Here's a related answer: React Hooks useCallback causes child to re-render
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With