React useMemo and useCallback

useMemo and useCallback are performance hooks. They prevent expensive recalculations and unnecessary function re-creations between renders. Both are optimization tools — they solve real problems when used in the right situations.

The Problem These Hooks Solve

Every time a React component re-renders, it runs the entire function body again. This means every variable gets re-created, every expression re-evaluates, and every function gets a brand new reference.

For most values, this is fine. But for expensive computations or functions passed as props to child components, unnecessary recreation wastes resources and causes child components to re-render even when nothing relevant changed.

useMemo: Memoize a Computed Value

useMemo caches the result of a calculation. It only recomputes when one of its dependencies changes.

Without useMemo — recalculates on every render

function ProductList({ products, filterText }) {
  // This runs on EVERY render, even if filterText didn't change
  const filtered = products.filter(p => p.name.includes(filterText));

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

With useMemo — recalculates only when filterText or products change

import { useMemo } from "react";

function ProductList({ products, filterText }) {
  const filtered = useMemo(() => {
    return products.filter(p => p.name.includes(filterText));
  }, [products, filterText]);

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

Diagram: useMemo as a result cache

Render 1: products=[...], filterText="laptop"
  → Run filter → Result = [Laptop A, Laptop B]
  → Cache the result

Render 2: products=[...], filterText="laptop" (SAME)
  → Dependencies unchanged → Return CACHED result
  → No filter runs!

Render 3: products=[...], filterText="phone" (CHANGED)
  → Dependency changed → Run filter again → New result
  → Update cache

useMemo Syntax

const memoizedValue = useMemo(() => {
  return computeExpensiveValue(a, b);
}, [a, b]);
  • First argument: a function that returns the value to cache
  • Second argument: dependency array — recompute when these change
  • Returns: the cached value

useCallback: Memoize a Function

useCallback caches a function itself (not its return value). Every render creates a brand new function reference. When you pass functions as props to child components, a new reference triggers unnecessary child re-renders.

Without useCallback

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

  // New function reference every render!
  const handleClick = () => {
    console.log("clicked");
  };

  // Child re-renders because handleClick is "new" every time
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Count: {count}</button>
      <Child onClick={handleClick} />
    </div>
  );
}

With useCallback

import { useCallback } from "react";

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

  // Same function reference across renders (unless dep changes)
  const handleClick = useCallback(() => {
    console.log("clicked");
  }, []); // No dependencies = never recreated

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

useCallback Syntax

const memoizedFn = useCallback(() => {
  doSomething(a, b);
}, [a, b]);
  • First argument: the function to cache
  • Second argument: dependency array — recreate function only when these change
  • Returns: the cached function

useMemo vs useCallback

Hook          What it caches          When to use
---------------------------------------------------------
useMemo       The RESULT of a fn      Expensive calculations
useCallback   The FUNCTION itself     Functions passed as props

// useMemo
const result = useMemo(() => computeValue(), [dep]);
// result is the return value

// useCallback
const fn = useCallback(() => doWork(), [dep]);
// fn is the function itself (not called yet)

A quick mental shortcut: useMemo(() => fn(), deps) is equivalent to useCallback(fn, deps) — they are the same mechanism applied differently.

Practical Example: Search + Sort

function DataTable({ data, searchQuery, sortField }) {
  // useMemo for expensive data transformation
  const processedData = useMemo(() => {
    return data
      .filter(row => row.name.toLowerCase().includes(searchQuery.toLowerCase()))
      .sort((a, b) => a[sortField] > b[sortField] ? 1 : -1);
  }, [data, searchQuery, sortField]);

  // useCallback for row click handler
  const handleRowClick = useCallback((id) => {
    console.log("Row clicked:", id);
  }, []); // No deps — function never changes

  return (
    <table>
      <tbody>
        {processedData.map(row => (
          <tr key={row.id} onClick={() => handleRowClick(row.id)}>
            <td>{row.name}</td>
            <td>{row.value}</td>
          </tr>
        ))}
      </tbody>
    </table>
  );
}

When NOT to Use These Hooks

Every use of useMemo or useCallback adds code complexity and has a small overhead for managing the cache. Do not use them:

  • On every function or value by default
  • For simple computations that run instantly
  • When the component is simple and re-renders are infrequent
  • Before you have identified a performance problem

The React team's own advice: measure first, then optimize. Use these hooks when you can prove there is a slowdown worth fixing.

React.memo: Memoize an Entire Component

React.memo wraps a component to prevent it from re-rendering unless its props actually change. It works well with useCallback — pass a stable function to a memoized child.

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

Without React.memo, the child re-renders whenever the parent renders. With it, the child re-renders only when its onClick prop is a different reference — which useCallback in the parent prevents.

Summary

useMemo caches the result of an expensive calculation and recomputes only when dependencies change. useCallback caches a function reference so passing it as a prop to child components does not trigger unnecessary re-renders. Both share the same dependency array mechanism. Use these hooks when profiling shows clear performance problems — not preemptively on every function or computed value. Combine them with React.memo on child components for the greatest effect.

Leave a Comment

Your email address will not be published. Required fields are marked *