useCallback hook is a React hook that returns a memoized callback function. It helps optimize functional components by keeping function references stable between renders so child components that depend on reference equality don't re-render unnecessarily.

Whenever a component re-renders, functions defined inside it are re-created. In many cases that's fine, but when those functions are passed as props to children (especially memoized children), recreating the function can trigger extra renders.

Use useCallback to memoize callbacks so they keep the same reference unless their dependencies change.

TL;DR: useCallback stabilizes function references to prevent avoidable renders — use it when passing callbacks to memoized children or when function identity matters.


What is useCallback Hook?

The useCallback hook is a performance optimization tool in React that allows you to memoize a callback function and recompute it only when its dependencies change.

useCallback is used to memorize a function, so it doesn't get recreated every time the component re-renders — unless one of its dependencies changes.


const memoizedFunction = useCallback(() => {
  // function logic
}, [dependencies]);
  • It returns the same function instance between re-renders (until dependencies change).
  • It helps prevent unnecessary re-renders in child components that receive the function as a prop.
  • It is particularly useful when passing functions to optimized child components that rely on reference equality to avoid re-renders (e.g., React.memo).

without useCallback

In the example where a calculation runs every time the component renders — even when it doesn't need to.


import { useState } from "react";

function Child({ onClick }) {
  console.log("Child re-rendered!");
  return ;
}

function Parent() {
  const [count, setCount] = useState(0);

  const handleClick = () => {
    console.log("Clicked!");
  };

  return (
    <div>
      <h2>Count: {count}</h2>
      <button onClick={() => setCount(count + 1)}>Increase Count</button>
      <Child onClick={handleClick} />
    </div>
  );
}

In this example, the Child component will re-render every time the Parent component re-renders, even if the onClick function hasn't changed. This can lead to performance issues in larger applications.


with useCallback

In this example, we use the useCallback hook to memoize the handleClick function, preventing unnecessary re-creations on each render.


import { useState, useCallback, memo } from "react";

const Child = memo(({ onClick }) => {
  console.log("Child re-rendered!");
  return ;
});

function Parent() {
  const [count, setCount] = useState(0);

  const handleClick = useCallback(() => {
    console.log("Clicked!");
  }, []); // function reference stays same unless dependencies change

  return (
    <div>
      <h2>Count: {count}</h2>
      <button onClick={() => setCount(count + 1)}>Increase Count</button>
      <Child onClick={handleClick} />
    </div>
  );
}

  

Now, the Child component will only re-render when the handleClick function changes (which it won't, since it has no dependencies). This optimization can significantly improve performance in larger applications.


useCallback with Dependencies

The useCallback hook can also accept a dependency array as its second argument. This array determines when the memoized function should be re-created. If any value in the array changes, the function is re-created; otherwise, the existing function is reused.


const memoizedFunction = useCallback(() => {
  // function logic
}, [dependency1, dependency2]);

In this example, the memoizedFunction will only be re-created if either dependency1 or dependency2 changes. This is useful for optimizing performance when the function relies on certain values that may change over time.


    const handleSubmit = useCallback(() => {
    console.log("Submitted:", formData);
}, [formData]);
    

In this example, the handleSubmit function will only be re-created if formData changes. This is useful for optimizing performance when the function relies on certain values that may change over time.


When to use useCallback

The useCallback hook is particularly useful in the following scenarios:

  • When passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders.
  • When you want to memoize a function definition to avoid re-creating it on every render.
  • When the function depends on specific props or state values, and you want to ensure it only changes when those values change.

By using useCallback in these scenarios, you can help optimize your React applications and prevent unnecessary re-renders.

When NOT to use useCallback

useCallback is not a free performance win — it adds memory overhead and some CPU work to manage the memoized function. Consider avoiding it when:

  • The child component is not memoized (React.memo) or does not care about function identity.
  • The callback is cheap to create and there are few re-renders — the optimization may not be worth the added complexity.
  • You prematurely optimize without measuring; always profile first for real bottlenecks.

Best practices

  • Profile before optimizing — use React DevTools and performance marks to confirm expensive re-renders.
  • Prefer keeping components small and pure; sometimes restructuring code removes the need for useCallback.
  • Use useCallback when passing callbacks to memoized children or when a function is part of a dependency list in other hooks.
  • Keep dependency arrays minimal and explicit — avoid including objects/arrays unless they're stable (use useMemo/useState).
  • Document why you added useCallback for future maintainers (small comment above the hook is helpful).

Following these practices helps you get the benefits of useCallback while avoiding common pitfalls.