useEffect Hook
useEffect lets you perform side effects in function components. Side effects include: data fetching, subscriptions, timers, manually updating the DOM, and logging.
Dependency Array โ The Key Concept
| Dependency array | When effect runs | Equivalent (class) |
|---|---|---|
| None (omitted) | After every render โ usually a bug! | componentDidUpdate always |
[] empty |
Once after first render only | componentDidMount |
[a, b] |
After first render, then when a or b changes | componentDidUpdate checking prev |
Data Fetching Pattern
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
// Cancellation flag โ prevents state update after unmount
let cancelled = false;
async function fetchUser() {
try {
setLoading(true);
setError(null);
const res = await fetch(`/api/users/${userId}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
if (!cancelled) setUser(data);
} catch (err) {
if (!cancelled) setError(err.message);
} finally {
if (!cancelled) setLoading(false);
}
}
fetchUser();
// Cleanup: cancel state updates if userId changes or component unmounts
return () => { cancelled = true; };
}, [userId]); // Re-run whenever userId changes
if (loading) return <Spinner />;
if (error) return <ErrorBanner message={error} />;
return <UserCard user={user} />;
}
Subscriptions & Event Listeners
function WindowSize() {
const [size, setSize] = useState({ w: window.innerWidth, h: window.innerHeight });
useEffect(() => {
const handler = () =>
setSize({ w: window.innerWidth, h: window.innerHeight });
window.addEventListener("resize", handler);
// Cleanup: remove listener when component unmounts
return () => window.removeEventListener("resize", handler);
}, []); // [] = add once, remove once
return <p>{size.w} ร {size.h}</p>;
}
Timers
function Countdown({ seconds }) {
const [remaining, setRemaining] = useState(seconds);
useEffect(() => {
if (remaining === 0) return;
const timer = setInterval(() => {
setRemaining(r => r - 1);
}, 1000);
return () => clearInterval(timer); // Cleanup!
}, [remaining]);
return <p>{remaining}s remaining</p>;
}
TipThe empty dependency array
[] is your most common choice. Think of it as: “set things up once when the component appears, tear them down when it disappears.”