I write a lot of React components which require code review. Many times during that process someone has dropped the classic line:

I think we should wrap this function in useCallback for performance reasons.

But what is useCallback and when does it make sense to use it?

useCallback refresher

To recap, useCallback is a React Hook which returns a memoized version of the callback function it is passed.

This means that the function object returned from useCallback will be the same between re-renders.

Why does this matter?

It’s worth recalling that in JavaScript, functions display referential equality. This means they are only considered equal if they point to the same object.

Therefore if you were two declare two functions with identical implementations they would not be equal to each other.

For example:

const firstFunction = function() {
    return 1 + 2; // same as secondFunction
}

const secondFunction = function() {
    return 1 + 2; // same as firstFunction
}

// Same implementation, different objects
firstFunction === secondFunction; // false 

// Same objects!
firstFunction === firstFunction; // true 

When is this useful in React?

Typically useCallback is helpful when passing callback props to highly optimised child components.

For example, if a child component that accepts a callback relies on a referential equality check (eg: React.memo() or shouldComponentUpdate) to prevent unnecessary re-renders when its props change, then it is important that any callback props do not change between renders.

To do this, the parent component can wrap the callback prop in useCallback and be guaranteed that it is passing the same function object down into the optimised child component.

function ParentComponent() {
    const onHandleClick = useCallback(() => {
        // this will return the same function
        // instance between re-renders
    });

    return (
        <MemoizedSubComponent
            handleClick={onHandleClick}
        />
    );
}

For more on this see my article Avoiding React component re-renders with React.memo.

When not to use useCallback

You should avoid seeing useCallback as a blanket performance optimisation.

In most cases, it’s simply better to accept that for functions declared inline with React components, each new re-render creates a new function object. This is typically absolutely fine and will not have a detrimental impact on performance.

You should always profile though – just in case!

In fact, as Kent C Dodds explains, in many cases the overhead of applying useCallback unnecessarily actually worsens the performance!

Remember: useCallback doesn’t memoize the function result

It’s worth noting that applying useCallback doesn’t memoize the result of a function’s invocation. Rather it memoizes the function object itself.

Consider the following:

function ParentComponent() {
    const onHandleClick = useCallback(() => {
        const special = calculatePi();
    });

    return (    
        <SubComponent
            handleClick={onHandleClick}
        />      
    );
}

In this example, each time the <SubComponent> triggers the onHandleClick callback via its handleClick prop the (presumably expensive!) calculatePi() will be triggered. The result of the arrow function isn’t memoized, only the reference.

If we wanted to avoid re-calculating Pi on each handleClick we’d be better off memorizing calculatePi directly via useMemo():

const memoizedPi = useMemo( calculatePii() );

Further reading

For more on useCallback I recommend taking a look at the following articles and guides:

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.