React
useEffect Hook
Master side effects in React components by fetching data, setting up subscriptions, and cleaning up resources with useEffect.
Think ofuseEffect as your component's personal assistant. It handles all the messy work — fetching data from APIs, setting up timers, subscribing to events. Stuff that doesn't belong in your render logic.
If you know regular JavaScript event listeners, useEffect works similarly. But instead of addEventListener, React calls your effect function at specific moments in your component's lifecycle.
Why Effects Matter
Pure functions are predictable. Same input, same output. But real apps need impure operations — API calls, DOM manipulation, timers. That's where effects come in. Consider DataFlow's revenue chart. The component renders immediately with placeholder data. Then useEffect kicks in, fetches real revenue numbers from the API, and updates the chart. Two separate concerns handled cleanly.Basic Effect Syntax
The simplest effect runs after every render. Not always what you want, but perfect for understanding the mechanics.// DataFlow stats component that logs render count
import { useState, useEffect } from 'react';
function StatsCard() {
const [revenue, setRevenue] = useState(0);
// Effect runs after every render
useEffect(() => {
console.log('StatsCard rendered');
document.title = `Revenue: $${revenue}`;
});
return (
<div style={{padding: '20px', background: '#ecfdf5', borderRadius: '12px'}}>
<h3>Revenue</h3>
<p>${revenue}</p>
<button onClick={() => setRevenue(revenue + 1000)}>
Add $1000
</button>
</div>
);
}What just happened?
The effect runs after every render — initial mount and each button click. Check your browser tab title and console. Try this: Click the button and watch both the displayed revenue and page title update.
Effect Dependencies
Running effects after every render kills performance. Most effects should run only when specific values change. That's where the dependency array comes in.// DataFlow component that fetches user count based on selected period
function UserStats() {
const [period, setPeriod] = useState('month');
const [userCount, setUserCount] = useState(0);
const [loading, setLoading] = useState(false);
// Effect runs only when period changes
useEffect(() => {
setLoading(true);
// Simulate API call delay
setTimeout(() => {
const counts = { month: 1250, quarter: 3800, year: 15400 };
setUserCount(counts[period]);
setLoading(false);
}, 800);
}, [period]); // Dependencies array
return (
<div style={{padding: '20px', background: '#faf5ff', borderRadius: '12px'}}>
<h3>Active Users ({period})</h3>
<select value={period} onChange={(e) => setPeriod(e.target.value)}>
<option value="month">This Month</option>
<option value="quarter">This Quarter</option>
<option value="year">This Year</option>
</select>
<p>{loading ? 'Loading...' : userCount.toLocaleString()}</p>
</div>
);
}What just happened?
The [period] dependency array tells React "only run this effect when period changes." The loading state shows while the simulated API call completes. Try this: Switch between time periods and notice the effect only runs when period actually changes.
Effect Cleanup
Some effects create subscriptions, timers, or event listeners. Clean them up when the component unmounts or before the effect runs again. Otherwise you get memory leaks.// DataFlow real-time order counter with cleanup
function OrderTracker() {
const [orders, setOrders] = useState(48);
useEffect(() => {
console.log('Setting up order tracker');
// Simulate real-time order updates
const interval = setInterval(() => {
setOrders(prev => prev + Math.floor(Math.random() * 3));
}, 2000);
// Cleanup function - runs when component unmounts
// or before effect runs again
return () => {
console.log('Cleaning up order tracker');
clearInterval(interval);
};
}, []); // Empty array = run once on mount
return (
<div style={{padding: '20px', background: '#fff7ed', borderRadius: '12px'}}>
<h3>Live Orders</h3>
<p style={{fontSize: '24px', fontWeight: 'bold', color: '#f97316'}}>
{orders}
</p>
<small>Updates every 2 seconds</small>
</div>
);
}What just happened?
The empty [] dependency array means "run once on mount." The cleanup function clears the interval when the component would unmount. Check your console for setup and cleanup logs. Try this: Watch the counter increment every 2 seconds.
Common Patterns
Real DataFlow components combine multiple patterns. Here's a comprehensive example showing data fetching, loading states, and error handling.// DataFlow revenue chart with complete effect patterns
function RevenueChart({ selectedYear }) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let cancelled = false; // Prevent state updates if component unmounts
async function fetchRevenue() {
try {
setLoading(true);
setError(null);
// Simulate API delay
await new Promise(resolve => setTimeout(resolve, 1000));
if (cancelled) return; // Don't update if cancelled
const mockData = {
2023: [120, 150, 180, 200, 175, 220, 240, 190, 210, 180, 160, 200],
2022: [100, 120, 140, 160, 145, 180, 200, 170, 185, 150, 140, 175]
};
setData(mockData[selectedYear]);
} catch (err) {
if (!cancelled) setError('Failed to fetch revenue data');
} finally {
if (!cancelled) setLoading(false);
}
}
fetchRevenue();
return () => { cancelled = true; }; // Cleanup
}, [selectedYear]);
if (loading) return <div>Loading revenue data...</div>;
if (error) return <div style={{color: '#dc2626'}}>{error}</div>;
const total = data.reduce((sum, month) => sum + month, 0);
return (
<div style={{padding: '20px', background: '#fef2f2', borderRadius: '12px'}}>
<h3>Revenue {selectedYear}</h3>
<p>Total: ${total}k</p>
<div style={{display: 'flex', gap: '4px', height: '60px', alignItems: 'end'}}>
{data.map((value, i) => (
<div key={i} style={{
background: '#dc2626',
width: '20px',
height: `${(value / 240) * 60}px`,
borderRadius: '2px'
}} />
))}
</div>
</div>
);
}What just happened?
This effect handles async data fetching with loading states, error handling, and cleanup to prevent memory leaks. The cancelled flag prevents state updates if the component unmounts during the API call. Try this: Switch between years and watch the loading states.
Multiple Effects
Components often need multiple effects for different concerns. Separate them instead of cramming everything into one useEffect. Easier to read and maintain.Best Practice
One effect per concern. Data fetching gets its own effect. Event listeners get another. Document title updates get a third. React optimizes multiple effects automatically.
Quiz
1. Your DataFlow user profile component needs to fetch user data when the userId changes but not on every render. What dependency array should you use?
2. Your DataFlow component sets up a timer with setInterval in useEffect. How do you prevent memory leaks when the component unmounts?
3. Your DataFlow component needs to fetch data, update the document title, and track analytics events. What's the best approach for organizing these effects?
Up Next: useState Hook
Master React's most essential hook for managing component state and triggering re-renders.