What Is the React useCallback Hook and When Should You Use It?

useCallback is a React Hook that memoizes function definitions between renders, returning the same function reference as long as its dependency values remain unchanged.

The useCallback hook plays a critical role in the React performance optimization toolkit. According to the facebook/react source code, this hook prevents unnecessary re-renders of child components and stabilizes callback references for effect dependencies. Understanding when and how to apply useCallback requires examining both its public API and internal implementation within the React reconciler.

How useCallback Works Under the Hood

React implements useCallback through a sophisticated hook slot architecture that persists function references across renders. The hook does not merely cache the function—it performs dependency tracking to determine when regeneration is necessary.

The Hook Slot Architecture

Internally, React stores the callback and its dependency list in a dedicated hook slot. In packages/react-reconciler/src/ReactFiberHooks.js, the implementation distinguishes between the initial mount and subsequent updates through two distinct functions: mountCallback and updateCallback. During the mount phase, React allocates a hook object that stores both the callback function and the dependency array. During updates, React retrieves this stored pair to perform comparison logic.

Shallow Comparison of Dependencies

The core mechanism driving useCallback is a shallow equality check of the dependency array. On each render, React compares the new dependency array with the previous one stored in the hook slot. If the shallow comparison determines that all dependencies are equal, React returns the cached function reference from the previous render. If any dependency has changed, React creates a new function instance, stores it in the hook slot along with the new dependencies, and returns the fresh reference.

React useCallback Syntax and Basic Usage

The public API for useCallback resides in packages/react/src/ReactHooks.js, where it is exported as part of the React Hooks interface. The hook accepts two parameters: the function to memoize and an array of dependencies.

const memoizedHandler = React.useCallback(
  (event) => {
    // Logic that may reference `count` or `props.value`
    console.log(count, props.value);
  },
  [count, props.value]   // Recreate only when these change
);

The function passed to useCallback is the callback you want to preserve. The dependency array follows the same rules as useEffect: it must include all values from the component scope that the callback uses and that could change over time. Omitting dependencies results in stale closures, while including unnecessary dependencies reduces the effectiveness of the memoization.

When to Use the useCallback Hook in React

Strategic use of useCallback targets specific architectural patterns where referential equality impacts performance or correctness. The hook is not a universal optimization but a targeted tool for stabilizing function references.

Memoized Child Components (React.memo)

When passing callbacks to child components wrapped in React.memo, useCallback prevents unnecessary re-renders. Without memoization, the parent creates a new function reference on every render, causing React.memo to see a changed prop and trigger a re-render of the child. By wrapping the callback in useCallback, the child receives the same reference when dependencies are stable, allowing React.memo to bail out of rendering.

Custom Hooks Dependencies

Custom hooks that accept callbacks as arguments or include them in dependency arrays benefit from stable references. Hooks like useEffect, useLayoutEffect, or useSubscription listed in packages/use-subscription/README.md treat callback changes as signals to re-run effects or re-subscribe. useCallback prevents these cycles when the logical behavior of the function hasn't changed.

Event Listeners and External APIs

When registering callbacks with event listeners or external libraries that add and remove handlers, useCallback ensures stable references. This prevents the performance cost of repeatedly detaching and re-attaching listeners on every render, as seen in packages/react-devtools-shell/src/app/ToDoList/ListItem.js.

React Concurrent Features

Concurrent React features like useTransition and useDeferredValue rely on stable callback references for optimal scheduling. Stable callbacks keep the scheduler's work list consistent across renders, preventing redundant task scheduling.

Performance Considerations and Premature Optimization

useCallback introduces memory overhead by storing function references and dependency arrays in hook slots. The shallow comparison of dependencies also consumes CPU cycles on every render. Therefore, applying useCallback indiscriminately to every function in a component often costs more than it saves.

Reserve useCallback for scenarios where you have measured performance issues related to referential inequality, or where the API contract explicitly requires stable references. Simple event handlers that are not passed to memoized children or effect dependencies rarely benefit from memoization.

Practical Code Examples

The following examples demonstrate useCallback patterns found in the React repository and real-world applications.

Memoizing Event Handlers in Parent Components

This example shows a basic counter where the increment handler is stable across renders:

function Counter() {
  const [count, setCount] = React.useState(0);

  // Stable callback – recreated only when `setCount` changes (which never does)
  const increment = React.useCallback(() => setCount(c => c + 1), []);

  return <button onClick={increment}>Clicks: {count}</button>;
}

Optimizing React.memo Child Components

This pattern from packages/react-devtools-shell/src/app/ToDoList/ListItem.js demonstrates preventing unnecessary child re-renders:

const ListItem = React.memo(
  ({item, onSelect}) => {
    console.log('render ListItem', item.id);
    return <li onClick={() => onSelect(item.id)}>{item.name}</li>;
  }
);

function List({items, onSelect}) {
  // Memoize the selector so ListItem props stay referentially equal
  const handleSelect = React.useCallback(id => onSelect(id), [onSelect]);

  return (
    <ul>
      {items.map(item => (
        <ListItem key={item.id} item={item} onSelect={handleSelect} />
      ))}
    </ul>
  );
}

Stabilizing Callbacks for Custom Subscription Hooks

This example aligns with the patterns documented in packages/use-subscription/README.md:

function useTimer() {
  const [seconds, setSeconds] = React.useState(0);

  // `subscribe` expects a stable callback; otherwise it will re‑subscribe each render
  const tick = React.useCallback(() => setSeconds(s => s + 1), []);

  React.useSubscription({
    getCurrentValue: () => seconds,
    subscribe: (callback) => {
      const id = setInterval(callback, 1000);
      return () => clearInterval(id);
    },
    // `tick` is passed as the subscription callback
    callback: tick,
  });

  return seconds;
}

Key Implementation Files in the React Repository

Understanding useCallback requires examining its implementation across the React codebase. The following files contain the authoritative source of truth for this hook's behavior:

Summary

  • useCallback memoizes function references between renders to maintain referential equality.
  • The hook stores callbacks in internal hook slots and performs shallow comparison of dependency arrays to determine whether to return a cached or new function instance.
  • Use useCallback when passing functions to React.memo children, supplying callbacks to useEffect dependencies, registering event listeners, or working with concurrent React features.
  • Avoid premature optimization—useCallback adds memory overhead and comparison costs that may exceed the benefits for simple, non-propagated functions.
  • The implementation resides in packages/react-reconciler/src/ReactFiberHooks.js (mountCallback/updateCallback) and is exposed via packages/react/src/ReactHooks.js.

Frequently Asked Questions

What is the difference between useCallback and useMemo?

While both hooks optimize performance through memoization, useCallback memoizes the function definition itself, returning the same function reference across renders. useMemo memoizes the result of a function execution, caching the return value rather than the function. Use useCallback when you need to prevent function recreation for prop comparison or dependency stability, and use useMemo for expensive computations whose results don't need recalculation.

Does useCallback prevent a component from re-rendering?

No, useCallback does not prevent the component that defines the callback from re-rendering. It only ensures that the function reference remains stable across that component's renders. The stabilization benefits child components that receive the callback as a prop (when combined with React.memo) or effects that list the callback in dependency arrays. The parent component still executes its full render cycle whenever state or props change.

When should I avoid using useCallback?

Avoid useCallback when the function is not passed to optimized child components, not used in dependency arrays, and not required by external APIs to have stable references. Adding useCallback to every function creates unnecessary memory allocation for the hook slot and CPU overhead for dependency array comparisons. Simple event handlers that are only used in JSX attributes, or functions defined inside effects that aren't reused, typically don't benefit from memoization.

How does React compare dependencies in useCallback?

React performs a shallow comparison (referential equality check using ===) between the current dependency array and the previous one stored in the hook slot. In packages/react-reconciler/src/ReactFiberHooks.js, the mountCallback and updateCallback functions implement this logic. If every element in the new array is referentially equal to the corresponding element in the stored array, React returns the cached function. If any element differs, React creates a new function instance and updates the stored reference.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s https://instagit.com/install.md

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →