On this page
useEffect
useEffect runs side effects after render — API calls, subscriptions, timers, and DOM manipulation.
Basic Syntax
import { useState, useEffect } from 'react';
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setSeconds(s => s + 1);
}, 1000);
return () => clearInterval(interval); // Cleanup
}, []); // Empty deps = run once on mount
return <p>Seconds: {seconds}</p>;
}
Dependency Array
// Run on every render
useEffect(() => { /* ... */ });
// Run once on mount
useEffect(() => { /* ... */ }, []);
// Run when count changes
useEffect(() => {
document.title = `Count: ${count}`;
}, [count]);
Data Fetching
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let cancelled = false;
async function fetchUser() {
try {
setLoading(true);
const res = await fetch(`/api/users/${userId}`);
if (!res.ok) throw new Error('Failed to fetch');
const data = await res.json();
if (!cancelled) setUser(data);
} catch (err) {
if (!cancelled) setError(err.message);
} finally {
if (!cancelled) setLoading(false);
}
}
fetchUser();
return () => { cancelled = true; }; // Cancel on unmount/re-fetch
}, [userId]);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return <div><h1>{user.name}</h1></div>;
}
Cleanup Function
Return a function from useEffect to clean up:
useEffect(() => {
function handleResize() {
console.log('Window resized');
}
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
Multiple Effects
Separate concerns with multiple useEffect calls:
function Dashboard({ userId }) {
// Fetch user
useEffect(() => {
fetchUser(userId);
}, [userId]);
// Fetch notifications
useEffect(() => {
fetchNotifications(userId);
}, [userId]);
// Analytics
useEffect(() => {
trackPageView('dashboard');
}, []);
}
Common Patterns
Sync with localStorage
useEffect(() => {
localStorage.setItem('theme', theme);
}, [theme]);
Focus input on mount
useEffect(() => {
inputRef.current?.focus();
}, []);
Avoid Common Mistakes
// WRONG — infinite loop
useEffect(() => {
setCount(count + 1);
}); // No dependency array
// WRONG — missing dependency
useEffect(() => {
fetchData(userId);
}, []); // Should include userId
// CORRECT
useEffect(() => {
fetchData(userId);
}, [userId]);
For complex data fetching, consider TanStack Query (React Query) which handles caching, refetching, and loading states automatically.