This post does not have a Yes/No answer, but tries to explain from a begineer standpoint why this hook is part of the official roster.
Let us start the story with two components:
- Parent
- Child
The parent component has a button that increments the count state in the same component while Child component will have nothing to do with it.
Note the console logs as you click re-render. Both child and parent will re-render with logs:
re-render parent component
re-render child component.
even though the child component has nothing to do with the state at all.
Now, we have to prevent the Child component from rerendering. Keeping the functional component we can use React.memo to achieve this. The child component will become:
import React, { memo } from "react";
const Child = memo(({ reset }) => {
// same content as earlier
});
Without the second argument, memo
will do a shallow comparison of props:
if(prevProps !== props) {
rerender();
} else {
// don't
}
You can check the logs now and see that it does not update the child component on parent rerender. It only updates the parent component with the log:
re-render parent component
Now, the requirements have progressed and we have to house a Reset button for count inside the Child
component.
This would refractor child to:
import React, { memo } from "react";
const Child = memo(({ reset }) => {
console.log("re-render child component.")
return (
<div>
<p>child component which resets count</p>
<button onClick={reset}>Reset Count</button>
</div>
);
});
export default Child;
For the reset function we have to refractor the parent to:
const Parent () => {
const [count, setCount] = useState(0);
console.log("re-render parent component");
const resetCount = () => {
setCount(0);
};
return (
<main>
<p>Count: {count}</p>
<button onClick={() => setCount(count=>(count+1))}>Increment</button>
<Child reset={resetCount} />
</main>
)
}
Now you can click on the reset button to reset the count to 0. But you will notice that the memo
magic that we applied earlier is not working anymore. The logs suggest that both child and parent are being rerendered.
Why is this happening?
As we mentioned earlier, memo
depends on the referential equality of prevProps
and props
to work. But the resetCount
function is being created on every render of Parent
and hence prevProps
and props
is not the same anymore (even though they are).
Now, to apply the memo
magic again, we need to make sure that resetCount
function is not unnecessarily recreated on every render of Parent
. This is exactly what useCallback
helps us to do.
const resetCount = useCallback(() => {
setCount(0);
}, [setCount]);
useCallback
will always return the same instance of the function on re-renders and would refresh only when dependencies change. Note the second argument of useCallback
, this is very similar to the useEffect
hook and refers to the dependecies that should trigger a reintialisation of the function inside useCallback
hook.
Finished Demo:
Crossposted from my blog
Extended Reading:
Top comments (3)
Thanks for sharing!
Is there any benefit to using useCallback without React.memo?
If the function you are passing down is going to be used as a
useEffect
(or some kind off) dependency in the Child, then yes.