Choose calculation, event or synchronization
Not all work belongs in an Effect. A full name derived from two fields can be calculated during render. Submission corresponding to a confirmation click belongs to that interaction’s logic.
A subscription that should exist while a component connects to an external system is a synchronization need. The example tracks viewport width, which can change independently of form events. That is why it uses an external listener.
Before adding an Effect, describe the system being synchronized and the condition that should start, update or end that relationship. If the work merely copies a derivable value into more state, review the data model first.
Setup and cleanup as a pair
Setup creates the external relationship and can return a cleanup function. When dependencies change after a commit, React runs cleanup with previous values before establishing new setup. Removing the instance also runs its corresponding cleanup.
In the example, measure is created inside setup and the returned function removes that same listener. Registering an additional empty handler or clearing only a variable does not release the previous subscription. For external connections, the same principle requires closing the connection created by that setup.
The unit of analysis is the concrete resource rather than merely the function name appearing in code.
Dependencies describing the logic
The list should include reactive values used by the logic, such as props and state. React compares each dependency with its previous value through Object.is; it does not perform arbitrary deep object comparison. Omitting the second argument requests another run after every commit. [] declares no reactive dependencies in that logic, rather than asking it to read new values without rerunning.
In the example, the Effect uses window and a stable-identity setter without reading width or props. If a connection came to depend on roomId, that prop would need to participate in the list. Silencing a warning to retain [] can hide a connection to the old room while the interface already displays another.
Fresh objects and avoidable reconnections
An options object created in the component body is new on every render even when it contains the same strings. If the Effect depends on that object, typing in another field can trigger cleanup and reconnection. A simple solution is to construct options inside the Effect and depend on the reactive values composing it.
If serverUrl is a module constant and roomId a prop, the list can be [roomId]. If serverUrl becomes a prop, it must also enter the analysis. The correction is not indiscriminately deleting dependencies; it is aligning structure with the configuration that actually governs the connection.
Refs for values that do not schedule rendering
useRef returns an instance-stable object with a mutable current property. Changing current does not tell React to render. Therefore width is state in the example, while inputRef holds the node reference for an imperative action.
After the input is created, React attaches the node to current; removing it can set current back to null. The focus handler considers that possibility. Do not use a ref as an automatic replacement for visual state or read and write current during render to evade purity, except in permitted initialization patterns.
The choice depends on whether change should determine presentation or merely retain a reference for later use.
Development, remount and paint
In Strict Mode, React can exercise an additional development setup and cleanup cycle to reveal symmetry problems. A didRun flag can reduce log messages without closing the connection when leaving. When the page mounts again, old resources can remain active.
The objective is for setup, cleanup and new setup to leave the correct relationship. Also avoid saying useEffect always runs after paint: documentation distinguishes interaction-related Effects and other cases. Effects run only on the client, not during server rendering.
The example starts with an unmeasured presentation and accesses window only inside the client Effect.
Diagnose resources and observations
For duplicated callbacks, identify how many subscriptions are active and which cleanup should remove each one. For an old connection, compare props read by the logic with its dependency list. For reconnection on every keystroke, look for objects or functions created during render whose identity changes.
These are different diagnoses and do not all call for a ref or []. Record state before and after mounting, changing configuration and leaving. The cases are reading-based decision models; they do not claim that a browser experiment was executed.
Asynchronous requests, out-of-order results and Suspense have their own boundaries in the next lesson.
import {useEffect, useRef, useState} from 'react';
export default function ViewportNote() {
const [width, setWidth] = useState(null);
const inputRef = useRef(null);
useEffect(() => {
function measure() { setWidth(window.innerWidth); }
measure();
window.addEventListener('resize', measure);
return () => window.removeEventListener('resize', measure);
}, []);
function focusNote() { inputRef.current?.focus(); }
return <section>
<p>Viewport: {width === null ? 'Waiting for client measurement' : width}</p>
<label>Local note <input ref={inputRef} name="note" /></label>
<button type="button" onClick={focusNote}>Focus note</button>
</section>;
}Setup measures client width and registers a listener. Cleanup removes that exact listener. width is state because presentation must react to measurement; inputRef permits focusing the mounted node on a click. [] is appropriate here because the logic reads no props or other reactive state.
Common pitfalls
Using [] to hide props that are read; depending on a fresh object every render; using a flag to avoid setup without cleanup; changing a ref and expecting rendering; guaranteeing every Effect runs after paint.
Related topics: Events, state snapshots and update queues · Reducers, context and shared state · Async data, Suspense and error boundaries
Effects synchronize the interface with external systems and need consistent dependencies and cleanup. Refs retain mutable values without scheduling rendering. Use state for data that should determine presentation.
Reference: Effect lifecycle and dependencies · React 19.3.0; JSX