Quiz

What is the `useCallback` hook in React and when should it be used?

Topics
React

TL;DR

useCallback caches a function definition while its dependencies remain Object.is-equal. The point is referential stability — so a memo-wrapped child can skip a render, or an Effect that depends on the function does not re-synchronize unnecessarily. Creating a function literal is usually not the cost worth optimizing; use useCallback when the changing identity causes measured or semantically required downstream work.

const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);

What is the useCallback hook in React and when should it be used?

useCallback is an identity cache for a function definition, useful only when stable identity prevents specific downstream work or satisfies a dependency relationship.

What is useCallback?

useCallback caches a function definition between renders until one of its dependencies changes according to Object.is. It does not avoid evaluating the function expression you pass; React returns the previously cached function identity when the dependencies match. That stable identity is useful only when it avoids downstream work or is required by another Hook's dependency relationship.

Syntax

Pass the function and every reactive value it reads:

const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);

When should useCallback be used?

The main cases involve memoized consumers or Effects that depend on the function's identity.

Preventing unnecessary re-renders of memoized children

When you pass a function as a prop to a React.memo-wrapped child, a new function reference on each parent render breaks the memoization and the child re-renders anyway. useCallback keeps the reference stable so React.memo can do its job.

This callback is correct, but its identity changes whenever count changes because it reads that render's state snapshot:

const handleClick = useCallback(() => {
setCount(count + 1);
}, [count]);

If the callback only reads count to calculate its next value, the functional updater form removes that dependency and keeps the callback stable across count updates:

import { memo, useCallback, useState } from 'react';
const ParentComponent = () => {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount((c) => c + 1);
}, []);
return <ChildComponent onClick={handleClick} />;
};
const ChildComponent = memo(({ onClick }) => {
console.log('ChildComponent rendered');
return <button onClick={onClick}>Click me</button>;
});

Stabilizing functions used in useEffect deps

If a function is referenced inside an effect's dependency array, a fresh reference each render will cause the effect to re-run every render. Wrapping the function in useCallback (or moving it inside the effect) prevents that:

const handleMessage = useCallback((event) => {
setMessages((messages) => [...messages, event.data]);
}, []);
useEffect(() => {
socket.addEventListener('message', handleMessage);
return () => socket.removeEventListener('message', handleMessage);
}, [socket, handleMessage]);

Relationship to useMemo

useCallback(fn, deps) is exactly equivalent to useMemo(() => fn, deps) — it memoizes a value that happens to be a function. Use useCallback for readability when the value is a function.

Caveats

Memoizing a function adds its own dependency comparison and maintenance cost:

  • It has a cost: useCallback adds a dependency comparison and more code. If no consumer relies on stable identity, leave the function inline.
  • The target is downstream work, not function allocation by itself. Profile the child render, Effect, or other identity-sensitive consumer that memoization is intended to protect.
  • Dependencies must be correct: missing dependencies cause stale closures; over-specified ones defeat the memoization.
  • It is a performance cache: React may discard the cached function for documented reasons such as suspending during the initial mount or editing the component in development. Do not rely on it for correctness.
  • React Compiler may replace it: in a Compiler-enabled codebase, start with plain inline functions and add manual memoization only when the Compiler does not optimize a measured hot path or a specific stable identity is required.

Further reading

Exercises

Check your understanding
Beta
Check your understanding Exercise
Check your understanding Exercise

Which situation provides a concrete reason to use useCallback?