useEffect runs after a commit
Rendering calculates what the interface should look like. React then commits the result to the DOM. After that commit, an Effect can connect the rendered component to a browser API, network connection, third-party widget, or another system React does not control.
import { useEffect, useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `Items: ${count}`;
}, [count]);
return (
<button onClick={() => setCount(count + 1)}>
Items: {count}
</button>
);
}
Call Hooks only at the top level of a component or custom Hook. Do not call useEffect inside a condition, loop, or event handler. Put the condition inside the Effect when setup should sometimes do nothing.
Effect dependencies
The dependency list describes every reactive value the Effect reads. Reactive values include props, state, and variables or functions declared inside the component. React compares each dependency with its previous value using Object.is.
- No dependency array
useEffect(setup)runs after every commit of the component. - Empty dependency array
useEffect(setup, [])does not depend on reactive values, so it normally runs once after mounting. Development Strict Mode performs an extra setup and cleanup cycle to test the logic. - Listed dependencies
useEffect(setup, [roomId])runs after mounting and again whenroomIdchanges.
serverUrl and roomId are reactive and must be listed because setup reads them.function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState(
"https://chat.example.com"
);
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => connection.disconnect();
}, [serverUrl, roomId]);
return (
<label>
Server URL
<input
value={serverUrl}
onChange={event => setServerUrl(event.target.value)}
/>
</label>
);
}
Do not omit a dependency just to stop an Effect from rerunning. That leaves the Effect using values from an older render. Change the code so the dependency is genuinely unnecessary, or make the setup and cleanup safe to repeat.
Cleanup reverses setup
An Effect may return a cleanup function. Before React runs the Effect again with changed dependencies, it first runs the previous cleanup. React also runs cleanup when the component is removed from the screen.
function OnlineStatus() {
const [isOnline, setIsOnline] = useState(navigator.onLine);
useEffect(() => {
function updateStatus() {
setIsOnline(navigator.onLine);
}
window.addEventListener("online", updateStatus);
window.addEventListener("offline", updateStatus);
return () => {
window.removeEventListener("online", updateStatus);
window.removeEventListener("offline", updateStatus);
};
}, []);
return <p>{isOnline ? "Online" : "Offline"}</p>;
}
Cleanup should mirror setup. Disconnect connections, unsubscribe listeners, clear timers, stop observations, or ignore obsolete async results. It should make a setup followed by cleanup indistinguishable from setup never having happened.
useEffect(() => {
let ignore = false;
async function loadResults() {
const results = await fetchResults(query);
if (!ignore) {
setResults(results);
}
}
loadResults();
return () => {
ignore = true;
};
}, [query]);
In development, Strict Mode intentionally runs setup, cleanup, and setup again after mounting. This exposes missing cleanup. Fix the cleanup instead of using a ref to hide the second setup.
External-system synchronization
Think of each Effect as one independent synchronization process. Its setup starts or updates that process using the current reactive values, and its cleanup stops the previous process.
- Connections and subscriptions
Connect to a chat room, subscribe to an event source, or observe browser state. Cleanup disconnects or unsubscribes.
- Imperative browser or third-party APIs
Keep a map, video player, dialog, or non-React widget synchronized with props and state.
- Network synchronization
Fetch data when a reactive query changes and ensure an obsolete response cannot replace newer results. A framework data API or client-side cache may handle this more efficiently.
import { useEffect, useRef } from "react";
function VideoPlayer({ src, isPlaying }) {
const videoRef = useRef(null);
useEffect(() => {
if (isPlaying) {
videoRef.current.play();
} else {
videoRef.current.pause();
}
}, [isPlaying]);
return <video ref={videoRef} src={src} controls />;
}
Effects run only on the client, not during server rendering. The initial server-generated HTML must make sense before an Effect has had a chance to synchronize anything.
Avoid unnecessary Effects
If no external system is involved, an Effect is usually the wrong tool. Calculating derived data in an Effect adds an extra render with stale data. Responding to a click in an Effect loses the direct connection between the interaction and the work.
Extra synchronization
State stores fullName, then an Effect updates it whenever its two source values change.
Calculate while rendering
A regular expression creates fullName from the current props with no extra state or render.
function Profile({ firstName, lastName, onSave }) {
// Derive during rendering, not in an Effect.
const fullName = `${firstName} ${lastName}`;
function handleSave() {
// The click caused this work, so keep it here.
onSave({ firstName, lastName });
}
return (
<section>
<h3>{fullName}</h3>
<button onClick={handleSave}>Save profile</button>
</section>
);
}
- Derive values during render.
Filter lists, combine names, and calculate booleans directly from current props and state. Use
useMemoonly if an expensive calculation needs caching. - Handle user actions in event handlers.
Submit forms, send purchase requests, and show interaction-specific notifications where the action occurs.
- Reset a subtree with a key when identity changes.
If a different record should get fresh local state, render the component with that record's stable ID as its
key. - Keep Effects for visibility-driven synchronization.
Analytics, subscriptions, and connections often belong in Effects because being rendered on screen is what causes the synchronization.
Check your understanding
-
An Effect reads
roomIdandserverUrl. Which dependencies should it list?Show answer
List both reactive values:
[roomId, serverUrl]. The dependency list describes the code, so every reactive value setup reads belongs there. -
When does React run an Effect's cleanup function?
Show answer
React runs the previous cleanup before rerunning setup after dependency changes, and once more when the component unmounts. Development Strict Mode also adds a setup and cleanup cycle to test the logic.
-
Should an Effect calculate
visibleItemsfromitemsandfilterand store the result in state?Show answer
Usually no. Calculate
visibleItemsduring rendering so it always matches the same props and state snapshot. ConsideruseMemoonly when the calculation is measurably expensive.