Skip to content

Modern React 10: Effects, synchronization, and cleanup

This is part 10 of the Modern React development series.

Effects connect React to systems outside the render tree. They are not a general place for all after-render logic. Their job is synchronization: start something external with the current props and state, then stop or update it when those values change.

Concept

useEffect runs after React commits UI to the screen. An Effect can subscribe to a browser API, connect to a service, start a timer, or coordinate with a non-React widget. Its cleanup function stops the previous synchronization.

Terms

  • Effect: A Hook callback used to synchronize with an external system after render commit.
  • Cleanup: A function returned from an Effect to stop the previous synchronization.
  • Dependency array: The list of reactive values that tell React when the Effect needs to resynchronize.
  • Reactive value: A prop, state value, or variable declared inside the component that can differ between renders.

Mental model

Think of each Effect as a plug. The Effect body plugs the component into an external system. The cleanup unplugs it before React plugs in the next version.

How it is used

Use Effects for subscriptions, browser events, timers, media APIs, analytics page visibility events, and external widgets. Use render calculations for derived values and event handlers for user-triggered work.

How to use it

  1. Name the external system the Effect synchronizes with.
  2. Read every reactive value the synchronization needs.
  3. Include those reactive values in the dependency array.
  4. Return cleanup when the external system needs to unsubscribe, disconnect, cancel, or clear.
  5. Split unrelated synchronization processes into separate Effects.

Example: Browser online status

import { useEffect, useState } from "react";
export function OnlineStatus() {
const [online, setOnline] = useState(navigator.onLine);
useEffect(() => {
const update = () => setOnline(navigator.onLine);
window.addEventListener("online", update);
window.addEventListener("offline", update);
return () => {
window.removeEventListener("online", update);
window.removeEventListener("offline", update);
};
}, []);
return <p>{online ? "Online" : "Offline"}</p>;
}
React output

Online

The component subscribes to browser events after commit and removes the listeners on cleanup.

Example: Chat room subscription

import { useEffect } from "react";
import { createChatConnection } from "./chat";
export function ChatRoom({ roomId }: { roomId: string }) {
useEffect(() => {
const connection = createChatConnection(roomId);
connection.connect();
return () => connection.disconnect();
}, [roomId]);
return <h1>Room {roomId}</h1>;
}
React output

Room general

When roomId changes, React cleans up the old connection and starts a new one for the next room.

Details to watch

  • External system: If there is no external system, the code often belongs in render or an event handler.
  • Dependencies: The dependency list describes values used by the synchronization, not values you would prefer to ignore.
  • Cleanup: Subscriptions, intervals, sockets, observers, and in-flight manual work usually need cleanup.
  • Strict Mode: Development Strict Mode may run setup and cleanup more than once to surface unsafe Effects.

Series navigation

References