Chapter 36: Synchronizing with Effects
Core Idea
An Effect lets a component synchronize itself with something outside React (a browser API, a server connection, a third-party widget) after rendering commits — unlike an event handler, an Effect isn't tied to one specific user interaction; it's tied to the component being displayed with a particular set of reactive values at all.
Key Concepts
- Effects vs. events: an event handler runs because the user did something specific (a click, a submit); an Effect runs because the component rendered (initially, and again whenever a dependency changes) — it's about staying synchronized with the outside world, not about responding to one interaction.
- Basic shape:
useEffect(() => { /* synchronization code */ return () => { /* cleanup */ }; }, [dependencies]) — the function body runs after commit; the optional returned function cleans up before the next run (or on unmount).
- The dependency array controls when it re-runs: omit it entirely and the Effect runs after every render; pass
[] and it runs only once after the initial mount; pass [a, b] and it re-runs whenever a or b changes between renders.
- Cleanup exists to undo the previous synchronization before starting a new one — e.g. a chat-room-connecting Effect's cleanup disconnects the old room before the next run connects to a new one; without cleanup, switching rooms would leave old connections open.
- Effects fire twice in development (Strict Mode) on purpose: mount → cleanup → mount again, specifically to surface Effects whose cleanup is missing or incorrect — an Effect that's properly written to synchronize/desynchronize should be unaffected by this double-run other than briefly.
- Writing a good Effect, in order: (1) write the synchronization logic assuming a single run, (2) specify the dependency array truthfully (every reactive value the Effect's body reads must be listed — see Ch 40), (3) add cleanup if the synchronization needs to be undone.
Code Examples
useEffect(() => {
const connection = createConnection(roomId);
connection.connect();
return () => connection.disconnect(); // cleanup: undo before next run/unmount
}, [roomId]);
- What it demonstrates: the connect/disconnect symmetry an Effect's body and cleanup should have — every "start doing X" needs a matching "stop doing X" so switching
roomId doesn't leak the old connection.
Key Takeaways
- Ask "does this run because of a specific interaction, or because the component needs to stay synced with something external?" — the former is an event handler, the latter is an Effect.
- Every dependency in the array should be there because the Effect's body actually reads it — not chosen to control timing (Ch 40 covers this in depth).
- The Strict Mode double-invoke in development is a bug detector, not a bug — if it breaks your Effect, the Effect's cleanup is incomplete.
Connects To
- Ch 37 (You Might Not Need an Effect): cases where reaching for
useEffect at all is the wrong call.
- Ch 38 (Lifecycle of Reactive Effects): the deeper mental model for how dependencies drive re-synchronization.
- Ch 53 (useEffect): the full API reference.