DevAcademy
LearnReactuseMemo & useCallback
IntermediateReact

useMemo & useCallback

Learn how to avoid unnecessary recalculations and re-renders using the useMemo and useCallback hooks.

Reading Time

18 min

Lesson

Lesson 32 of 42

The Problem: Repeated Work on Every Render

A component re-runs its entire function body on every render. Any expensive calculation inside it re-runs too — even if none of the values it depends on actually changed.

useMemo: Memoizing a Value

useMemo(calculateValue, dependencies) re-runs an expensive calculation only when one of its dependencies actually changes, reusing the cached result otherwise.

Memoizing an Expensive Calculation

import { useMemo } from "react";

function ProductList({ products, filter }) {
  const filteredProducts = useMemo(() => {
    console.log("Filtering...");
    return products.filter((p) => p.category === filter);
  }, [products, filter]);

  return (
    <ul>
      {filteredProducts.map((p) => <li key={p.id}>{p.name}</li>)}
    </ul>
  );
}

useCallback: Memoizing a Function

Every render creates brand new function instances for anything defined inside a component — including event handlers. useCallback(fn, dependencies) returns the same function reference between renders as long as its dependencies haven’t changed.

Memoizing a Callback

import { useCallback } from "react";

function TodoList({ todos }) {
  const handleToggle = useCallback((id) => {
    console.log("Toggled:", id);
  }, []); // same function reference across every render

  return todos.map((todo) => (
    <TodoItem key={todo.id} todo={todo} onToggle={handleToggle} />
  ));
}

Why Function Identity Matters

useCallback is most useful when a function is passed as a prop to a child wrapped in React.memo — without it, a new function reference on every render defeats React.memo’s comparison, causing the child to re-render anyway even though nothing meaningful changed.

useCallback Paired with React.memo

const TodoItem = React.memo(function TodoItem({ todo, onToggle }) {
  console.log("Rendering:", todo.text);
  return <li onClick={() => onToggle(todo.id)}>{todo.text}</li>;
});
// Without useCallback on onToggle, TodoItem re-renders every time
// the parent renders, even if 'todo' itself hasn't changed.

Memoization is a Performance Tool, Not a Default

useMemo and useCallback have their own small cost (comparing dependencies on every render) and add code complexity. Reach for them when you’ve identified an actual performance problem — usually via the React DevTools Profiler — not preemptively on every value and function.

React Compiler Reduces the Need for Manual Memoization

Newer versions of React ship with a compiler that can automatically add memoization where it’s beneficial, reducing how often useMemo and useCallback need to be written by hand.

Best Practice

Don’t reach for useMemo/useCallback by default. Profile first, then memoize specifically where a measured re-render or slow calculation is causing a real, noticeable problem.

Interview Questions

Quick Quiz

1. What does useMemo do?

2. What does useCallback memoize?

3. Why is useCallback often paired with React.memo?