Skip to content

React Tutorial React Hooks

The useCallback Hook: Stable Function References

Lesson 67 of 156 2 min read By Deepak Paswan Updated Oct 10, 2026

Runnable lesson · press Try it on the examples to edit and run them here.

useCallback is useMemo for functions. It returns the same function object between renders unless its dependencies change.

Why would a function identity matter?

In JavaScript, a function created inside a component is a new function on every render, even if the code is identical. Usually that does not matter. It matters in two cases:

  1. You pass the function to a child wrapped in React.memo, which skips re-rendering when its props are the same. A new function every time means the props are always “different”.
  2. The function is a dependency of an effect or another hook, so a new function re-runs it every render.

The syntax

App.jsx
JSX
const handleSave = useCallback(() => {
  save(data);
}, [data]);

A demo you can count

memo makes a child re-render only when its props change. We count the child’s renders:

App.jsx
JSX
let childRenders = 0;

const Child = React.memo(function Child({ onPing }) {
  childRenders++;
  return <button onClick={onPing}>Ping</button>;
});

function App() {
  const [typed, setTyped] = React.useState(0);
  const [pings, setPings] = React.useState(0);

  // Same function between renders, as long as nothing in [] changes
  const handlePing = React.useCallback(() => setPings((p) => p + 1), []);

  return (
    <div>
      <button onClick={() => setTyped(typed + 1)}>Type a letter</button>
      <p>Typed: {typed}, Pings: {pings}</p>
      <Child onPing={handlePing} />
      <p>Child renders: {childRenders}</p>
    </div>
  );
}

Click Type a letter several times: the parent re-renders, but the Child does not, because handlePing is the same function each time. The number under the button is read while the parent renders, so it first shows 0 (the child has not rendered yet) and then 1 after the next parent render, and it stays at 1. Try replacing useCallback(...) with a plain arrow function: the child re-renders on every click.

Without memo, useCallback does nothing useful

If the child is not memoized, it re-renders whenever the parent does, no matter what you pass. So useCallback is useful only together with memo, or when the function is a hook dependency.

When you do not need it

  • Passing a handler to a normal DOM element (<button onClick={...}>). The browser element does not care.
  • Small components that render quickly.
  • When you have not measured a problem.

Writing useCallback everywhere adds noise and can even slow things down. The same advice as in useMemo applies: measure first. The React Compiler also handles much of this automatically when it is enabled.

Alternatives that need no hook

  • Move the function outside the component if it does not use props or state.
  • Use an updater function (setCount(c => c + 1)) so the function needs no dependencies.
  • Restructure so the expensive child receives data, not callbacks.

The dependency array

Like other hooks, list everything the function reads (props, state). If you forget one, the function sees stale values. This is the same stale-closure problem described in Effect Dependencies.

Key points

  • useCallback(fn, deps) returns a stable function until a dependency changes.
  • It helps only with memoized children or hook dependencies.
  • Do not add it by default; measure and consider the React Compiler.