useMemo/useCallback recomputes every render even though the dependency "didn't change"
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.
Comments
Loading comments...