React useEffect Hook
Synchronize components with external systems, manage dependencies, and prevent memory leaks with cleanup functions.
TL;DR
- Synchronize components with external browser or network APIs using
useEffect. - List all referenced reactive variables explicitly inside the
dependencyarray. - Prevent memory leaks by returning a dedicated
cleanupcancellation function.
Dependency Array Behaviors
Mount Only EffectEmpty array runs effect once on initial mount.
useEffect(() => {
console.log('Component mounted!');
}, []);Reactive DependencyRuns on mount and whenever listed dependencies change.
useEffect(() => {
document.title = `User: ${userId}`;
}, [userId]);No Dependency ArrayRuns after every single render cycle without restriction.
useEffect(() => {
console.log('Rendered!');
});Cleanup Functions
Window Event ListenerRemove event listener during unmount to prevent leaks.
useEffect(() => {
const onResize = () => setWidth(window.innerWidth);
window.addEventListener('resize', onResize);
const clean = () => {
window.removeEventListener('resize', onResize);
};
return clean;
}, []);Interval TimerClear active interval when component unmounts.
useEffect(() => {
const id = setInterval(tick, 1000);
return () => clearInterval(id);
}, []);WebSocket ConnectionClose active socket connection cleanly on teardown.
useEffect(() => {
const socket = createSocket(roomId);
return () => socket.disconnect();
}, [roomId]);Data Fetching Patterns
AbortController FetchCancel in-flight HTTP request when dependencies change.
useEffect(() => {
const c = new AbortController();
fetch(`/api/user/${id}`, { signal: c.signal })
.then((res) => res.json())
.then(setData)
.catch((err) => {
if (err.name !== 'AbortError') setError(err);
});
return () => c.abort();
}, [id]);Boolean Flag CancellationIgnore stale async responses if user switched tabs.
useEffect(() => {
let active = true;
fetchData().then((res) => {
if (active) setData(res);
});
return () => { active = false; };
}, [query]);Async IIFE WrapperInvoke asynchronous function immediately inside effect.
useEffect(() => {
async function load() {
const res = await fetch('/api');
setData(await res.json());
}
load();
}, []);Common Pitfalls
Infinite Render LoopSetting state inside effect without dependency guard.
// BAD: Triggers infinite loop
useEffect(() => {
setCount((c) => c + 1);
});Stale Closure BugMissing state dependency reads frozen initial value.
// BAD: count missing
useEffect(() => {
const log = () => console.log(count);
const timer = setInterval(log, 1000);
return () => clearInterval(timer);
}, []);Unneeded EffectCompute derived state directly during render instead.
// BETTER: No effect needed
const fullName = `${first} ${last}`;Tips
- Always list every reactive value referenced inside the effect within the
dependenciesarray to satisfy the linter rules. - Return a cleanup function to unsubscribe from sockets, remove
eventListenertriggers, or abort in-flight network requests.
Warnings
- Omitting dependencies causes stale closures where effects observe outdated
propsor state snapshots during execution. - Do not use
useEffectto transform data for rendering when standard inline derived variables calculate values instantly.
In Practice
Fetch remote user details with automatic request cancellation on ID change.
- Instantiate an AbortController instance inside the effect.
- Pass controller signal into the native fetch request.
- Set data on resolution while ignoring abort errors.
- Return cleanup function calling controller.abort().
export function UserProfile({ id }: { id: string }) {
const [user, setUser] = useState<User | null>(null);
useEffect(() => {
const c = new AbortController();
fetch(`/api/users/${id}`, { signal: c.signal })
.then((res) => res.json())
.then((data) => setUser(data))
.catch((err) => {
if (err.name !== 'AbortError') {
console.error(err);
}
});
return () => c.abort();
}, [id]);
if (!user) return <Spinner text="Loading user..." />;
return <CardTitle>{user.name}</CardTitle>;
}FAQ
The cleanup function runs immediately before the effect re-executes with new dependencies, and once more when the component completely unmounts from the DOM tree.
No. The effect callback must return either nothing or a cleanup function. To run async operations, define an async function inside the effect and invoke it immediately.
React StrictMode deliberately mounts, unmounts, and remounts components in development to help you detect missing cleanup functions and accidental side effect bugs.