What is the `useCallback` hook in React and when should it be used?
TL;DR
useCallback returns a memoized function whose identity only changes when one of its dependencies changes. The point is referential stability — so a React.memo-wrapped child does not re-render, or a useEffect whose deps include the function does not re-fire. Re-creating a plain function literal each render is essentially free; the actual cost being avoided is the downstream work triggered by a new reference.
const memoizedCallback = useCallback(() => {doSomething(a, b);}, [a, b]);
Note for 2026: with the React Compiler (stable in React 19), most components no longer need manual
useCallback/useMemo/React.memo— the compiler memoizes automatically. New code targeting a compiler-enabled project should generally not reach foruseCallbackunless profiling shows a specific need.
What is the useCallback hook in React and when should it be used?
What is useCallback?
The useCallback hook is a React hook that returns a memoized version of the callback function that only changes if one of the dependencies has changed. It is useful for optimizing performance by preventing unnecessary re-creations of functions.
Syntax
const memoizedCallback = useCallback(() => {doSomething(a, b);}, [a, b]);
When should useCallback be used?
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.
A common bug is depending on a state value you also update inside the callback — the dependency changes after every click, so the memoization is useless:
// BAD: `count` is in the deps, so `handleClick` gets a new identity every click,// defeating React.memo on the child.const handleClick = useCallback(() => {setCount(count + 1);}, [count]);
The fix is to use the functional updater form of setState, which lets you drop the value from the deps:
const ParentComponent = () => {const [count, setCount] = useState(0);// GOOD: empty deps, stable identity for the lifetime of the component.const handleClick = useCallback(() => {setCount((c) => c + 1);}, []);return <ChildComponent onClick={handleClick} />;};const ChildComponent = React.memo(({ onClick }) => {console.log('ChildComponent rendered');return <button onClick={onClick}>Click me</button>;});
Stabilising 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 fetchData = useCallback(async () => {const res = await fetch(`/api/items?query=${query}`);setItems(await res.json());}, [query]);useEffect(() => {fetchData();}, [fetchData]);
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
- It is not free:
useCallbackitself adds bookkeeping. For a function that is not passed to a memoized child or used in a dependency array, wrapping it usually makes the code slower, not faster. - The cost being avoided is downstream re-renders, not the function literal itself. Allocating an arrow function on each render is essentially free.
- Dependencies must be correct: missing dependencies cause stale closures; over-specified ones defeat the memoization.
- The React Compiler removes most need for it: in a compiler-enabled codebase, prefer plain inline functions and let the compiler memoize.