What is a Side Effect?
Rendering should be a pure calculation of JSX from props and state. A side effect is anything that reaches outside that calculation — fetching data, subscribing to an event, manually touching the DOM, or starting a timer.
A Basic useEffect
import { useEffect, useState } from "react";
function DocumentTitle({ title }) {
useEffect(() => {
document.title = title;
}, [title]);
return <h1>{title}</h1>;
}The Dependency Array
The second argument to useEffect tells React when to re-run the effect. It re-runs whenever any value in that array changes between renders.
Dependency Array Behavior
| Dependency Array | When the Effect Runs |
|---|---|
| Omitted entirely | After every single render |
| [] (empty array) | Only once, after the first render |
| [a, b] | After the first render, and again whenever a or b changes |
Fetching Data on Mount
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`/api/users/${userId}`)
.then((res) => res.json())
.then((data) => setUser(data));
}, [userId]); // re-fetches whenever userId changes
if (!user) return <p>Loading...</p>;
return <h2>{user.name}</h2>;
}Cleanup Functions
If an effect returns a function, React calls it before the effect runs again, and once more when the component unmounts — the right place to cancel subscriptions, clear timers, or remove event listeners to avoid memory leaks.
Cleaning Up an Interval
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const id = setInterval(() => {
setSeconds((s) => s + 1);
}, 1000);
return () => clearInterval(id); // cleanup: runs on unmount
}, []);
return <p>{seconds}s elapsed</p>;
}Missing Dependencies Cause Stale Values
If an effect uses a prop or state value but omits it from the dependency array, the effect can keep using an outdated ("stale") value from an earlier render. Most editors with the eslint-plugin-react-hooks rule will warn about this automatically.
Not Every Side Effect Needs useEffect
Event handlers (like a click handler making an API call) don’t need useEffect — they already run in response to a specific user action. useEffect is specifically for synchronizing with something external whenever the component renders with new values.
Best Practice
Always include every value from component scope that your effect uses in the dependency array. If that causes the effect to re-run too often, that’s usually a sign the effect (or the surrounding state) needs restructuring, not that the dependency should be omitted.