Jun 30, 2022

React: useMemo vs useCallback

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! 🙂

Loading Comments...