useMemo/useCallback recomputes every render even though the dependency "didn't change"

Reactjs·Sep 13, 2026·intermediate·
Quick answer

React compares dependency array entries by reference (Object.is), not deep equality. An object, array, or function literal created inline on every render is a new reference every time, even if its contents look identical — so useMemo/useCallback never actually skips recomputation. Memoize the dependency itself, or move the literal outside the component/render path.

What causes this error

React's dependency-array comparison is a shallow reference check, not a deep-equality check by design (deep comparison would be far more expensive than the memoization it's meant to enable). An inline object literal (`{ id }`), array literal (`[a, b]`), or arrow function passed as a dependency is a brand-new reference on every render — so even though its shape never changes, the comparison always reports "changed."

The Fix

// Wrong — options is a new object every render, defeating useMemo
function List({ id }) {
  const options = { id, sortBy: 'name' }; // new reference every render
  const sorted = useMemo(() => sortItems(items, options), [options]);
}

// Fix — depend on the primitive values that actually matter
function List({ id }) {
  const sortBy = 'name';
  const sorted = useMemo(
    () => sortItems(items, { id, sortBy }),
    [id, sortBy] // primitives compare by value, not reference
  );
}

Common causes / variations

  • Passing an inline object/array literal as a prop or dependency instead of primitive values
  • An inline arrow function passed as a dependency without being wrapped in its own `useCallback`
  • A dependency computed from `.filter()`/`.map()` on every render, producing a new array reference each time even with identical contents

Related errors

See also: React Compiler cannot compile component, Maximum update depth exceeded.

Was this fix helpful?

Comments

Loading comments...