What is useCallback Hook in React?

This article provides a comprehensive overview of the useCallback Hook in React, explaining its purpose, how it works, and how it helps optimize application performance. You will learn the fundamental syntax of useCallback, see practical code examples, and discover the best practices for when to use—and when to avoid—this React Hook to prevent unnecessary component re-renders.

Understanding the Problem: Function Recreation

In React, every time a component re-renders, all the functions defined inside that component are recreated from scratch. While JavaScript engines can create functions quickly, this behavior causes performance issues when these functions are passed as props to optimized child components.

Because functions in JavaScript are objects, they are compared by reference, not by value. If a parent component re-renders, it creates a new instance of the callback function. Even if the child component is optimized using React.memo, it will detect a “new” prop reference and trigger an unnecessary re-render.

What is the useCallback Hook?

The useCallback Hook solves this issue by caching (or memoizing) a function definition between renders. Instead of creating a new function instance on every single render, React will return the same cached function instance as long as its dependencies have not changed.

The Syntax

The useCallback Hook accepts two arguments: the inline function you want to cache, and an array of dependencies.

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

How useCallback Prevents Unnecessary Re-renders

To see useCallback in action, consider a scenario where a parent component passes a click handler function down to a child list component.

Without useCallback

In this example, the Button component will re-render every time the ParentComponent state changes, because handleClick is recreated on every render.

import React, { useState } from 'react';

// Child component wrapped in React.memo
const Button = React.memo(({ handleClick }) => {
  console.log('Button rendered');
  return <button onClick={handleClick}>Click Me</button>;
});

function ParentComponent() {
  const [count, setCount] = useState(0);
  const [otherState, setOtherState] = useState(false);

  // Recreated on every render
  const handleClick = () => {
    setCount((prev) => prev + 1);
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setOtherState(!otherState)}>Toggle State</button>
      <Button handleClick={handleClick} />
    </div>
  );
}

With useCallback

By wrapping the handler in useCallback and passing an empty dependency array, the function reference remains identical across renders. Clicking the “Toggle State” button will no longer cause the child Button to re-render.

import React, { useState, useCallback } from 'react';

const Button = React.memo(({ handleClick }) => {
  console.log('Button rendered');
  return <button onClick={handleClick}>Click Me</button>;
});

function ParentComponent() {
  const [count, setCount] = useState(0);
  const [otherState, setOtherState] = useState(false);

  // Cached across renders
  const handleClick = useCallback(() => {
    setCount((prev) => prev + 1);
  }, []); // Empty array means the function never changes

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setOtherState(!otherState)}>Toggle State</button>
      <Button handleClick={handleClick} />
    </div>
  );
}

useCallback vs. useMemo

It is easy to confuse useCallback with useMemo as both are optimization hooks. The difference lies in what they cache:

// useCallback returns the function
const logValue = useCallback(() => console.log(value), [value]);

// useMemo returns the evaluated result of the function
const computedValue = useMemo(() => expensiveCalculation(value), [value]);

When to Use and When to Skip useCallback

You should not wrap every function in your codebase with useCallback. Memoization has its own performance overhead because React must do extra work to compare dependencies on every render.

Use useCallback when:

  1. Passing functions to optimized child components: When a child component is wrapped in React.memo and expects a callback function as a prop.
  2. Function is a dependency in other Hooks: If the function is used inside a useEffect, useMemo, or another useCallback, keeping the reference stable avoids infinite loops or unnecessary hook executions.

Avoid useCallback when:

  1. The child component is not memoized: If the child component does not use React.memo, it will re-render anyway, making useCallback redundant.
  2. Simple components: For small, lightweight components where re-rendering has negligible performance costs.