Capítulo 40 de 116

Chapter 40: Removing Effect Dependencies

Core Idea

A "fix" that involves removing or suppressing a dependency the linter flags is almost never right — the correct fix is nearly always to change the Effect's code so that value genuinely doesn't need to be a dependency, not to hide the dependency from the array.

Key Concepts

  • Dependencies should match the code, always — the array isn't a knob you tune for desired timing; it's a mechanical consequence of what the Effect body reads. If you want an Effect to re-run less often, the fix is to make the Effect body read fewer reactive values, not to omit values it does read from the array.
  • Do you need this dependency at all? Common reasons a value ends up an "unwanted" dependency: it's actually an Effect Event candidate (Ch 39 — reads latest value without needing reactivity), it's an object/function recreated fresh every render (see below), or the Effect is doing two unrelated things that should be split into two Effects with different dependency sets.
  • Objects and functions as dependencies are a frequent trap: an object or function literal created inside the component body is a new reference on every render, so [options] where options = { roomId } re-triggers the Effect every render even though the meaningful content didn't change. Fixes: move the object/function creation inside the Effect itself if nothing else needs it, or depend on the primitive values (roomId) instead of the wrapper object.
  • Wrap a function passed as a dependency in useEffectEvent if it needs to read the latest props/state without itself becoming a reactive dependency (this is exactly Ch 39's pattern, applied here as a dependency-removal technique).
  • If an Effect needs a value only to decide "did this actually change," compare against a ref inside the Effect rather than adding the value as a dependency that would trigger a full re-run on every change.

Code Examples

// ❌ `options` is a new object every render → Effect re-runs every render
const options = { serverUrl, roomId };
useEffect(() => { /* connect using options */ }, [options]);

// ✅ Depend on the primitive values the Effect actually needs
useEffect(() => {
  const options = { serverUrl, roomId }; // created fresh inside the Effect, fine
  /* connect using options */
}, [serverUrl, roomId]);
  • What it demonstrates: moving object construction inside the Effect body so the dependency array can list the underlying primitives instead of an unstable wrapper reference.

Key Takeaways

  1. Never suppress the exhaustive-deps lint rule to "fix" an unwanted re-run — treat the warning as correct and change the code instead.
  2. Objects/functions built fresh in the component body are the single most common source of unnecessary Effect re-runs — depend on their primitive contents, not the wrapper reference.
  3. If an Effect seems to need conflicting dependency behavior (react to X, but not to Y even though Y is read), that's usually the Effect Event pattern from Ch 39, not a dependency-array workaround.

Connects To

  • Ch 38 (Lifecycle of Reactive Effects): the "dependencies mirror what the body reads" rule this chapter operationalizes.
  • Ch 39 (Separating Events from Effects): the primary tool for values that must stay current without being reactive.
  • Ch 47 (ESLint Rules Reference): the exhaustive-deps rule that catches dependency-array mistakes automatically.