Jun 30, 2022
React: useMemo vs useCallback
First of all let's quickly explain what is useMemo
and useCallback
in React.
Both useMemo
and useCallback
are React hooks that have to do with a term called Memoization.
According to Wikipedia:
In computing, memoization or memoisation is an optimization technique used primarily to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again.
A memoized function "remembers" the results corresponding to some set of specific inputs. As a result, subsequent calls with past inputs return the remembered result rather than recalculating it.
For example consider a costly function f(n)
that calculates the factorial of a number n
. We know that the final result of the function is invariant - e.g the factorial of 3 would always be 6. So why don't we just store the result and return the stored result the next time this function is called with input 3? That's memoization.
useMemo in React
Suppose we've got a React component that needs to run an expensive function that computes a value. The parameters of that function are taken from the component's props - so we can't move that function outside of the component.
That means the function will be invoked on every re-render of the component:
const Component = (props) => {
const {a, b, ...otherProps} = props;
const x = computeExpensiveValue(a, b);
return <div>{x}</div>
}
That implementation would be catastrophical for the performance of our component and the UI most probably would feel unresponsive.
🎉 useMemo to the rescue!
With useMemo
we can memoize the result of computeExpensiveValue
function. That means the function will only re-run when input parameters a or b change and not on every re-render.
The syntax is really simple, we just pass an array of dependencies to the useMemo
hook. useMemo
will only recompute the memoized value when one of the dependencies has changed.
If no array is provided, a new value will be computed on every render.
const Component = (props) => {
const {a, b, ...otherProps} = props;
const x = useMemo(() => computeExpensiveValue(a, b), [a, b]);
return <div>{x}</div>
}
Of course we've got to be careful with useMemo and only use it as a performance optimization, as React states:
You may rely on useMemo as a performance optimization, not as a semantic guarantee. In the future, React may choose to “forget” some previously memoized values and recalculate them on next render, e.g. to free memory for offscreen components. Write your code so that it still works without useMemo — and then add it to optimize performance.
useCallback in React
OK, now that we've understood useMemo, let's talk a bit about the useCallback
hook.
This hook is useful when we want to memoize functions (callbacks).
The most common use case we would want to memoize a function is when we pass it (the function) down to pure child components.
For example consider the scenario below:
const PureComponent = React.memo((props) => {
const {onToggleClick, ...otherProps} = props;
return <button onClick={() => onToggleClick()}>some button</button>
});
const Father = (props) => {
const {a, b} = props;
return <div>
<PureComponent {...otherProps} onToggleClick={() => console.log(a, b)} />
{/* ... other stuff here ...*/}
</div>
}
We want our PureComponent
to re-render only when it's props change - that's why we wrapped it in a React.memo
call. React.memo works by shallowly compare the prop changes.
However, when Father
component re-renders the onToggleClick
function is re-created - it does not have a stable reference. As a result, all our React.memo optimization is pointless.
🎉 useCallback to the rescue!
By using the useCallback
hook, we can instruct onToggleClick
function to have a stable reference in each re-render.
const onToggleClick = useCallback(() => console.log(a, b), []);
We aren't finished yet. You may noticed an array as a second parameter to useCallback
. This is the dependency array that will dictate to useCallback
to change whenever one of the dependencies change - in this way onToggleClick
will always have access to the latest value of a and b.
As React states:
... every value referenced inside the callback should also appear in the dependencies array ...
So the final implementation would be something like:
const PureComponent = React.memo((props) => {
const {onToggleClick, ...otherProps} = props;
return <button onClick={() => onToggleClick()}>some button</button>
})
const Father = (props) => {
const {a, b} = props;
const onToggleClick = useCallback(() => console.log(a, b), [a, b]);
return <div>
<PureComponent {...otherProps} onToggleClick={onToggleClick} />
{/* ... other stuff here ...*/}
</div>
}
So in a nutshell:
useMemo
is to memoize a calculation between a function's calls and between renders.useCallback
is to memoize a callback itself (referential equality) between renders.
Finally we have to note here that:
useCallback(fn, deps)
is equivalent to:
useMemo(() => fn, deps)
The end
I hope you found the article informative.
See you soon! 🙂
Newsletter
Subscribe to my mailing list
Subscribe to get my latest content by email. I won't send you spam, I promise. Unsubscribe at any time.