LearnContact
Lesson 3222 min read

useCallback Hook

Learn how the useCallback Hook memoizes function definitions to keep stable references across renders, especially for memoized child components.

Introduction

In JavaScript, every time a function is written inside another function, a brand-new function is created — even if the code inside it looks identical to the last one. Inside a React component, that means every render creates new function instances for any handler you define, like onClick={() => doSomething()}. Normally this is harmless. But it becomes a real problem when that new function is passed down as a prop to a child component that is trying to avoid unnecessary re-renders.

The useCallback Hook solves this by memoizing the function itself, returning the exact same function reference across renders as long as its dependencies have not changed. This lesson explains why that matters, how to use useCallback correctly, and how it relates to useMemo from the previous lesson.

What You Will Learn
  • Why a new function reference is created on every render.
  • What useCallback does and how it keeps a stable function reference.
  • Why this matters specifically with React.memo child components.
  • The syntax for useCallback and a worked example.
  • The difference between useMemo (memoizing a value) and useCallback (memoizing a function).

The Problem: New Functions on Every Render

Consider a parent component that renders a memoized child and passes it a callback prop. Even if the child is wrapped in React.memo — which skips re-rendering when its props are unchanged — passing a freshly created function on every render defeats that optimization, because React.memo compares props with a shallow equality check, and two different function instances are never equal, even if they do the exact same thing.

The problem: a new function every render
const Child = React.memo(function Child({ onClick }) {
  console.log("Child rendered");
  return <button onClick={onClick}>Click me</button>;
});

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

  // A new function is created on every Parent render
  const handleClick = () => console.log("Clicked!");

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <Child onClick={handleClick} />
    </div>
  );
}
Console Output
Child rendered   (logged again every time Increment is clicked,
                  even though Child's visible output never changes)

Because handleClick is a brand-new function every time Parent renders, Child sees a "different" onClick prop each time and re-renders anyway, even though it is wrapped in React.memo.

What is useCallback?

useCallback returns a memoized version of a function that only changes if one of its dependencies changes. Instead of creating a new function on every render, React hands back the same function reference from a previous render, as long as the dependency array has not changed.

Stable Reference

useCallback returns the same function object across renders when dependencies are unchanged.

Pairs with React.memo

Its main use case is passing stable callbacks to memoized child components.

Dependency-Driven

A new function is only created when a value in the dependency array changes.

Not for Every Function

Functions that are not passed to memoized children rarely need useCallback.

useCallback Syntax

useCallback takes a function and a dependency array, and returns a memoized version of that same function.

Basic syntax
import { useCallback } from "react";

const memoizedCallback = useCallback(() => {
  doSomething(a, b);
}, [a, b]);
A Useful Way to Think About It
  • useCallback(fn, deps) is roughly equivalent to useMemo(() => fn, deps).
  • The dependency array works exactly like useEffect and useMemo — list every value the function reads from the outer scope.
  • The returned function itself never runs automatically; you still call it (or pass it as a prop) like any function.

Example: Callbacks and React.memo

Let's fix the earlier example by wrapping handleClick in useCallback. Now the same function reference is passed to Child across renders, so React.memo can correctly skip re-rendering it when count changes.

Parent.jsx (fixed with useCallback)
import { useCallback, useState } from "react";

const Child = React.memo(function Child({ onClick }) {
  console.log("Child rendered");
  return <button onClick={onClick}>Click me</button>;
});

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

  const handleClick = useCallback(() => {
    console.log("Clicked!");
  }, []); // no dependencies, so the same function is reused forever

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <Child onClick={handleClick} />
    </div>
  );
}
Console Output
Child rendered            (only logged once, on the first render)

Clicking "Increment" repeatedly updates Count but no longer
logs "Child rendered" again — Child correctly skips re-rendering.

useMemo vs useCallback

useMemo and useCallback solve the same underlying problem — avoiding unnecessary work between renders — but they memoize different things: useMemo memoizes the return value of a function, while useCallback memoizes the function definition itself.

useMemo — memoizes a VALUE

  • const total = useMemo(() => {
  • return items.reduce((sum, i) => sum + i.price, 0);
  • }, [items]);
  • // total is a number

useCallback — memoizes a FUNCTION

  • const handleAdd = useCallback(() => {
  • addItem(newItem);
  • }, [newItem]);
  • // handleAdd is a function
Rule of Thumb

If you are caching the result of a calculation, use useMemo. If you are caching a function so it keeps the same reference — usually to pass to a memoized child or another Hook's dependency array — use useCallback.

Common Mistakes

Avoid These Mistakes
  • Wrapping every function in useCallback even when it is never passed to a memoized child or another Hook — this adds overhead for no benefit.
  • Forgetting that the child must also be wrapped in React.memo; useCallback alone does not stop a plain child component from re-rendering.
  • Missing a dependency, causing the memoized function to close over stale values.
  • Confusing useCallback(fn, deps) with useMemo(() => fn, deps) — they are nearly equivalent, but useCallback is the clearer, more idiomatic way to memoize a function.

Best Practices

  • Reach for useCallback mainly when passing a function to a React.memo-wrapped child, or as a dependency of another Hook like useEffect.
  • Always pair useCallback on the parent with React.memo on the child — one without the other rarely helps.
  • List every outer variable the function uses in the dependency array.
  • Do not sprinkle useCallback across every event handler in every component "just in case."
  • Profile before optimizing — the same rule that applies to useMemo applies here.

Frequently Asked Questions

Does useCallback stop the function from ever recreating?

No. It stops the function from being recreated only when its dependencies have not changed. If a dependency changes, useCallback returns a new function, just like a fresh one would be created each render normally.

Is useCallback useless without React.memo?

In most cases, yes — its main benefit is preserving a stable reference so a memoized child (or another Hook's dependency array) does not see a "different" function on every render.

Can I use useCallback for functions inside useEffect dependencies?

Yes. If a function is used inside useEffect and is also defined in the component, wrapping it in useCallback can prevent the effect from re-running unnecessarily.

What is the difference between useCallback(fn, deps) and useMemo(() => fn, deps)?

They produce the same result, but useCallback is the more readable, purpose-built version for memoizing functions specifically.

Should I use useCallback for inline arrow functions in JSX like onClick={() => ...}?

Usually not necessary unless that specific handler is passed to a memoized child component — otherwise the cost of memoizing outweighs the benefit.

Key Takeaways

  • useCallback memoizes a function definition, returning the same reference across renders when dependencies are unchanged.
  • It matters most when passing callbacks to React.memo-wrapped child components.
  • Syntax: useCallback(() => { ... }, [deps]).
  • useMemo memoizes a computed value; useCallback memoizes a function — the same underlying mechanism, different targets.
  • Like useMemo, it should be used to fix a real, observed re-render problem, not applied everywhere by default.

Summary

useCallback keeps a function's identity stable across renders, which matters most when that function is handed down to a child component wrapped in React.memo. Without it, a brand-new function reference on every render can silently cancel out the benefit of memoizing the child.

In this lesson, you saw the re-render problem useCallback solves, its syntax, a worked React.memo example, and how it differs from useMemo. Next, you will learn about custom Hooks, which let you extract and reuse your own stateful logic across components.

Lesson 32 Completed
  • You understand why new function references are created on every render.
  • You can use useCallback to keep a stable reference for a memoized child.
  • You wrote a worked React.memo + useCallback example.
  • You can clearly explain the difference between useMemo and useCallback.
Next Lesson →

Custom Hooks