TL;DR;
useMemo
is to memoize a calculation result between a function's calls and between renders
useCallback
is to memoize a callback itself (referential equality) between renders
useRef
is to keep data between renders (updating does not fire re-rendering)
useState
is to keep data between renders (updating will fire re-rendering)
Long version:
useMemo
focuses on avoiding heavy calculation.
useCallback
focuses on a different thing: it fixes performance issues when inline event handlers like onClick={() => { doSomething(...); }
cause PureComponent
child re-rendering (because function expressions there are referentially different each time)
This said, useCallback
is closer to useRef
, rather than a way to memoize a calculation result.
Looking into the docs I do agree it looks confusing there.
useCallback
will return a memoized version of the callback that only changes if one of the inputs has changed. This is useful when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders (e.g. shouldComponentUpdate).
Example
Suppose we have a PureComponent
-based child <Pure />
that would re-render only once its props
are changed.
This code re-renders the child each time the parent is re-rendered — because the inline function is referentially different each time:
function Parent({ ... }) {
const [a, setA] = useState(0);
...
return (
...
<Pure onChange={() => { doSomething(a); }} />
);
}
We can handle that with the help of useCallback
:
function Parent({ ... }) {
const [a, setA] = useState(0);
const onPureChange = useCallback(() => {doSomething(a);}, []);
...
return (
...
<Pure onChange={onPureChange} />
);
}
But once a
is changed we find that the onPureChange
handler function we created — and React remembered for us — still points to the old a
value! We've got a bug instead of a performance issue! This is because onPureChange
uses a closure to access the a
variable, which was captured when onPureChange
was declared. To fix this we need to let React know where to drop onPureChange
and re-create/remember (memoize) a new version that points to the correct data. We do so by adding a
as a dependency in the second argument to `useCallback :
const [a, setA] = useState(0);
const onPureChange = useCallback(() => {doSomething(a);}, [a]);
Now, if a
is changed, React re-renders the component. And during re-render, it sees that the dependency for onPureChange
is different, and there is a need to re-create/memoize a new version of the callback. Finally everything works!
NB not just for PureComponent
/React.memo
, referential equality may be critical when use something as a dependency in useEffect
.