useEffect runs its callback after React has committed the render to the DOM, making it the correct place for side effects that shouldn't happen directly during rendering โ the second argument, a dependency array, controls when the effect re-runs: omitting it entirely re-runs the effect after every render, an empty array [] runs it only once after the initial mount, and a populated array [a, b] re-runs it whenever any listed dependency changes between renders. Returning a function from the effect callback registers a cleanup function, which React calls right before the effect runs again, and once more when the component unmounts, making it the standard place to cancel subscriptions, clear timers, or otherwise undo whatever the effect set up.
1Understanding useEffect
useEffect runs its callback after React has committed the render to the DOM, making it the correct place for side effects that shouldn't happen directly during rendering โ the second argument, a dependency array, controls when the effect re-runs: omitting it entirely re-runs the effect after every render, an empty array [] runs it only once after the initial mount, and a populated array [a, b] re-runs it whenever any listed dependency changes between renders. Returning a function from the effect callback registers a cleanup function, which React calls right before the effect runs again, and once more when the component unmounts, making it the standard place to cancel subscriptions, clear timers, or otherwise undo whatever the effect set up.
An empty dependency array, [], means an effect runs once on mount and its cleanup runs once on unmount โ but forgetting to include a variable the effect actually uses inside that array is one of the most common React bugs, since the effect then keeps referencing a stale, outdated value from the render when it was first created.
import { useState, useEffect } from 'react';
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const id = setInterval(() => setSeconds(s => s + 1), 1000);
return () => clearInterval(id);
}, []);
return <p>Seconds: {seconds}</p>;
}2Practical Example
Here is a real-world application of useEffect showing how it is used in production React code.
import { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [name, setName] = useState(null);
useEffect(() => {
console.log('Fetching user', userId);
fetchUser(userId).then(user => setName(user.name));
}, [userId]);
return <p>{name ?? 'Loading...'}</p>;
}3Best Practices
Follow these guidelines when working with useEffect:
1. Include every value from the component's scope that the effect actually reads inside the dependency array, rather than omitting some to avoid extra re-runs
2. Return a cleanup function from useEffect whenever the effect sets up something ongoing, like a subscription, timer, or event listener, that needs to be undone later
3. Use an empty dependency array, [], specifically for effects that should run exactly once, on mount, like an initial data fetch that doesn't depend on any changing value
Tip: An empty dependency array, [], means an effect runs once on mount and its cleanup runs once on unmount โ but forgetting to include a variable the effect actually uses inside that array is one of the most common React bugs, since the effect then keeps referencing a stale, outdated value from the render when it was first created.
import { useState, useEffect } from 'react';
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const id = setInterval(() => setSeconds(s => s + 1), 1000);
return () => clearInterval(id);
}, []);
return <p>Seconds: {seconds}</p>;
}4You Might Not Need an Effect
A value that can be computed directly from existing props or state โ a total, a filtered list, a formatted string โ should be a plain expression in the component body, not a separate piece of state kept in sync by useEffect. Syncing a derived value this way costs an extra render (the effect runs, calls a setter, and triggers a second render) and is one of the most common React anti-patterns.
Before adding a useEffect, ask whether the value could just be a `const` computed during render. If the answer is yes, you don't need the effect.
// Unnecessary
useEffect(() => setTotal(price * qty), [price, qty]);
// Just calculate it
const total = price * qty;5Race Conditions & the Ignore Flag
When an effect's dependency changes before an in-flight async request resolves, a slower, outdated response can arrive after a newer one and silently overwrite fresh state with stale data. Guard against this by tracking staleness with a local flag set inside the cleanup function, or by cancelling the previous request with an AbortController.
useEffect(() => {
let ignore = false;
fetchUser(id).then(data => { if (!ignore) setUser(data); });
return () => { ignore = true; };
}, [id]);6Objects, Arrays & Referential Equality
React compares each dependency to its previous value with Object.is, which checks objects and arrays by reference, not content. An object or array literal created inside the component body is a brand-new reference on every render, so listing it directly in a dependency array re-runs the effect on every render โ depend on the underlying primitive value instead.
If an effect seems to re-run more often than its dependencies actually change, check whether one of those dependencies is an object or array literal recreated on every render.
// New object every render, effect re-runs constantly
useEffect(() => {...}, [{ id }]);
// Depend on the primitive instead
useEffect(() => {...}, [id]);7Effects vs. Event Handlers
Code that responds to a specific user interaction โ a click, a form submission, a key press โ belongs in an event handler, not an effect. Event handlers fire because of a particular interaction; effects fire because the component rendered, or one of its dependencies changed, regardless of what (if anything) caused that change.
<button onClick={handleSave}>Save</button>
// vs.
useEffect(() => { sync(); }, [state]);8The Golden Question
Before reaching for useEffect, ask one question: 'Am I synchronizing this component with something outside React?' If yes โ a browser API, a subscription, a timer, a network request โ useEffect is the right tool. If you're only calculating a value from existing props and state, or responding to a specific interaction, you almost certainly don't need an effect at all.
// Am I syncing with something external?
// YES -> useEffect
// NO -> calculate during render, or use an event handler