30% offEnding soon
useDeepCompareEffectLoading saved progress…

useDeepCompareEffect

useDeepCompareEffect is a useEffect that re-runs its effect only when the dependencies change by value — a deep comparison — instead of by reference. React's own useEffect compares each dependency with Object.is, which is exactly right for a string or number but a trap for an object or array: a dependency built fresh each render ([{ query, page }]) is a new reference every time, so the effect fires on every render — a fetch per keystroke elsewhere in the component, forever. This hook compares the dependency's contents, so the effect runs only when something actually changed.

Signature

function useDeepCompareEffect(
  effect: () => void | (() => void),
  deps: unknown[],
): void;
// Same call shape as useEffect. Runs `effect` after mount, then again only when
// a DEEP comparison of `deps` against the previous `deps` finds a difference.

Examples

// `filters` is a config object you don't control the shape of. A plain
// useEffect refetches on every render; this refetches only when its value moves.
useDeepCompareEffect(() => {
  fetchResults(filters);
}, [filters]);
// An inline object literal — a fresh reference each render — with a cleanup.
useDeepCompareEffect(() => {
  const socket = connect({ room, token });
  return () => socket.close();
}, [{ room, token }]); // reconnects only when room or token actually change

Notes

  • Same signature as useEffect — the effect may return a cleanup function, and deps is the array. Nothing about the call site changes.
  • Deep, not shallow — a change nested any number of levels down counts; a brand-new object with identical contents does not.
  • deepEqual is provided — a complete deep-equality function is handed to you in the starter (it is the Deep Equal question, finished). Wire it in; don't re-implement it.
  • You cannot pass deps straight to useEffect — that is the naive version, and it re-runs every render for an object dependency. Building the mechanism that fixes it is the whole exercise.
  • All-primitive deps are a smell — if every dependency is a string, number, or boolean, a plain useEffect already does the right thing and is cheaper.
  • Out of scope — no custom-comparator argument and no layout-effect variant; see the solution's Going further.