← React & TypeScript

Hooks

What are useMemo and useCallback for? When would you use them and when would they be a premature optimization?

Show answer — try answering out loud first

useMemo memoizes the RESULT of a computation and useCallback memoizes a FUNCTION, and both keep the same reference between renders as long as their dependencies don't change. They're used when a computation is expensive or when referential equality matters (for example, with React.memo or an effect's dependencies); in all other cases they're usually noise.

On each render, the component runs again: all the objects, arrays, and functions you declare inside are created AGAIN, with new references. To JavaScript, () => {} !== () => {}.

The key points:

  • useMemo(fn, deps): runs fn and stores its result. On subsequent renders, if the dependencies didn't change, it returns the stored value without recomputing.
  • useCallback(fn, deps): stores the function itself. useCallback(fn, deps) is equivalent to useMemo(() => fn, deps).
  • Referential equality: React.memo, dependency arrays, and internal comparisons use Object.is. A "new" object or function on each render breaks those comparisons even if its content is identical.
  • When YES: genuinely expensive computations (filtering/sorting large lists), or values/functions passed to a child wrapped in React.memo or that are a dependency of a useEffect.
  • When NOT: memoizing trivial sums, strings, or handlers that go to a normal <button>. Each memo has a cost (memory and comparing deps) and clutters the code. Measure first, then optimize.
  • Relationship with React.memo: memoizing the callback only helps if the child that receives it is wrapped in React.memo; otherwise, the child re-renders anyway.
import { memo, useCallback, useMemo, useState } from "react";

type Producto = { id: number; nombre: string; precio: number };

type FilaProps = { producto: Producto; onSeleccionar: (id: number) => void };

// React.memo: only re-renders if its props change by reference/value
const Fila = memo(function Fila({ producto, onSeleccionar }: FilaProps) {
  return (
    <li onClick={() => onSeleccionar(producto.id)}>
      {producto.nombre} — ${producto.precio}
    </li>
  );
});

function Catalogo({ productos }: { productos: Producto[] }) {
  const [filtro, setFiltro] = useState("");
  const [seleccionado, setSeleccionado] = useState<number | null>(null);

  // useMemo: only re-filters when productos or filtro change,
  // not when "seleccionado" changes
  const visibles = useMemo(
    () => productos.filter(p => p.nombre.toLowerCase().includes(filtro.toLowerCase())),
    [productos, filtro]
  );

  // useCallback: same reference between renders -> React.memo works
  const seleccionar = useCallback((id: number) => {
    setSeleccionado(id);
  }, []);

  return (
    <div>
      <input value={filtro} onChange={e => setFiltro(e.target.value)} />
      <p>Seleccionado: {seleccionado ?? "ninguno"}</p>
      <ul>
        {visibles.map(p => (
          <Fila key={p.id} producto={p} onSeleccionar={seleccionar} />
        ))}
      </ul>
      {/* Expected behavior: when selecting, the rows do NOT re-render */}
    </div>
  );
}

Wrapping the child in React.memo but passing it a new function on each render, canceling out the optimization:

// BAD: the arrow function is recreated on each render of the parent,
// so React.memo on Fila never prevents anything
<Fila producto={p} onSeleccionar={(id) => setSeleccionado(id)} />

// GOOD: stable reference with useCallback
const seleccionar = useCallback((id: number) => setSeleccionado(id), []);
<Fila producto={p} onSeleccionar={seleccionar} />

The opposite error is also common: filling the component with useMemo/useCallback "just in case." If nobody compares the reference and the computation is cheap, you just added complexity.

Both memoize things between renders: useMemo stores the result of a computation and useCallback stores the function itself, and both keep the same reference as long as the dependencies don't change. They matter because on each render everything is recreated with new references, and that breaks optimizations based on referential equality, like React.memo or dependency arrays. I use them in two cases: expensive computations, like filtering large lists, and values or functions passed to memoized children or to a useEffect. Beyond that I consider them premature optimization: they have their own cost and I prefer to measure before memoizing.

Quick challenge

You have const numeros: number[] with thousands of elements and a state tema: "claro" | "oscuro" that changes often. Compute the sum of the numbers without it being recomputed when the theme changes.

See solution
import { useMemo, useState } from "react";

function Resumen({ numeros }: { numeros: number[] }) {
  const [tema, setTema] = useState<"claro" | "oscuro">("claro");

  // Only recomputes if the reference of "numeros" changes;
  // changing the theme reuses the memoized value
  const suma = useMemo(() => numeros.reduce((acc, n) => acc + n, 0), [numeros]);

  return (
    <div className={tema}>
      <p>Suma: {suma}</p>
      <button onClick={() => setTema(t => (t === "claro" ? "oscuro" : "claro"))}>Cambiar tema</button>
    </div>
  );
}