Capítulo 38 de 116

Chapter 38: Lifecycle of Reactive Effects

Core Idea

An Effect has its own lifecycle, separate from the component's — think of it as "start synchronizing" / "stop synchronizing," a cycle that repeats every time a value it depends on changes, rather than trying to reason about it in terms of component mount/update/unmount.

Key Concepts

  • Think per-render, not per-component. Each render "sees" its own version of props/state and thus its own version of the Effect's dependency values; the Effect from that render synchronizes for that specific set of values, then hands off to the next render's Effect. This differs from class-component lifecycle thinking, where componentDidMount/componentDidUpdate felt like a single continuous timeline.
  • Every reactive value read inside the Effect must be a dependency. A reactive value is anything that can change between renders — props, state, and anything computed from them. The dependency array isn't something you "choose" for timing purposes; it's mechanically determined by what the Effect body actually reads. Lying about it (omitting a value the body uses) is the source of most stale-closure bugs.
  • Each render's Effect is independent — the cleanup from render N's Effect runs before render N+1's Effect body, forming a start→stop→start→stop chain across time, one link per change to a dependency.
  • Effects "react" to reactive values: this is why the dependency array can't be picked freely — an Effect that reads roomId and theme but only lists [roomId] will keep using a stale theme from whichever render it last actually ran in, because the closure captured that render's theme value.
  • Non-reactive values don't need to be dependencies: a value declared outside the component (a true module-level constant) or something guaranteed never to change across renders doesn't need to appear in the array — but props, state, and anything derived from them generally do.

Code Examples

useEffect(() => {
  const connection = createConnection(serverUrl, roomId);
  connection.connect();
  return () => connection.disconnect();
}, [serverUrl, roomId]); // every reactive value the body reads, listed
  • What it demonstrates: the dependency array mechanically mirrors the reactive values used inside — serverUrl and roomId are both listed because the Effect body reads both.

Key Takeaways

  1. Stop thinking "when does this run relative to mount/update" and start thinking "what values does this Effect need to stay synchronized with" — the dependency array follows directly from that.
  2. An Effect + its cleanup form one repeating unit (start/stop), not two separate lifecycle hooks bolted together.
  3. A linter (the ESLint plugin's exhaustive-deps rule, Ch 47) can mechanically verify the dependency array matches what the Effect body reads — treat its warnings as correctness signals, not suggestions to suppress.

Connects To

  • Ch 36 (Synchronizing with Effects): the foundational start/cleanup shape this chapter explains the timing of.
  • Ch 39 (Separating Events from Effects): what to do with logic inside an Effect that should not be reactive to every dependency.
  • Ch 40 (Removing Effect Dependencies): practical techniques for when a dependency is technically required but causes unwanted re-runs.