The React docs claims that "useCallback is a React Hook that lets you cache a function definition between re-renders."
How is it possible to cache function definitions? This example is taken from React docs:
import { useCallback } from 'react';
export default function ProductPage({ productId, referrer, theme }) {
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}, [productid, referrer]);
How is it possible to prevent the callback passed to useCAllback
from being created? As far as I know, the JS runtime will create a new instance of the callback each time the ProductPage
function is called (I have tested this behavior in JavaScript code).
What is useCallback
optimizing? I also assumed that React calls the ProductPage
function on every render.
I have searched Stackoverflow and google. I expect an explanation on why using useCallback
is beneficiary.
2
Answers
It’s not possible, you’re right on that. But that’s not what
useCallback
is aiming at.The optimisation that
useCallback
enables is when usinghandleSubmit
(i.e. the return value ofuseCallback
) as a dependency of other hooks, e.g.useMemo
oruseEffect
. But on its own, it’s pretty useless.Let’s re-order the questions a little:
It is not possible. The function
ProductPage
is called every time the component is instantiated, which creates a new instance of the(orderDetails) => { ... }
callback.But – when we use
useCallback
, React uses the previous value unless any of the deps has changed, essentially throwing away the new instance.It’s caching the value of the function itself. A standard dynamic cache would require an extra unique key to store/retrieve the cached value; since React’s rules of hooks ensure that each instance of a hook within a component instance has a fixed position, there is no explicit cache key required.
useCallback
stabilises the callback value – essentially, the function doesn’t change unless it needs to change because one of the dependencies has changed.On its own, this doesn’t optimise anything – there is, of course, a cost associated with tracking the cached value and its dependencies. If, however, the callback is passed to other components or hooks, then it may cause re-rendering or other work. A self-contained if slightly contrived example:
If we don’t stabilise
notifyAll
usinguseCallback
, then the value of the callback changes every time the hook is rendered, even if theinstances
andvalue
have stayed the same.The
useCallback
docs do go into better explanations of the situations in which stabilising a function usinguseCallback
(and similarlyuseMemo
) can reduce the amount of work done in re-renders, in particular a couple of deep dives towards the bottom.