Effects & Data Fetching
The useEffect hook lets you run side effects — operations that reach outside React's render, like fetching data, setting up subscriptions, timers, or manually touching the DOM.
import { useState, useEffect } from "react";
function Clock() {
const [time, setTime] = useState(new Date());
useEffect(() => {
const id = setInterval(() => setTime(new Date()), 1000);
return () => clearInterval(id); // cleanup
}, []); // empty deps → runs once on mount
return <p>{time.toLocaleTimeString()}</p>;
}
The dependency array controls when it runs
useEffect(() => { /* ... */ }); // after EVERY render
useEffect(() => { /* ... */ }, []); // ONCE after mount
useEffect(() => { /* ... */ }, [userId]); // when `userId` changes
Always list every value from the component scope that the effect uses in the dependency array. The eslint-plugin-react-hooks rule checks this for you.
The cleanup function
If your effect sets up something ongoing (timer, subscription, event listener), return a cleanup function. React runs it before the next effect and when the component unmounts — preventing memory leaks.
Fetching data
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
let active = true;
setLoading(true);
fetch(`/api/users/${userId}`)
.then((res) => res.json())
.then((data) => {
if (active) { setUser(data); setLoading(false); }
});
return () => { active = false; }; // ignore stale responses
}, [userId]);
if (loading) return <p>Loading…</p>;
return <h1>{user.name}</h1>;
}
In modern apps, data fetching is often handled by libraries like TanStack Query or by frameworks like Next.js (Server Components), which remove most manual useEffect fetching. But understanding the pattern is essential.
Watch & Learn
A recommended video to watch alongside this chapter.
More “Effects & Data Fetching” videos on YouTube