This content originally appeared on DEV Community and was authored by Marvin Roque
After watching myself struggle with useEffect, let me break down what's actually happening under the hood.
The Lifecycles You Need to Know
Think of your component like a house:
- Mounting = Building the house (component first appears)
- Rendering = Redecorating (component updates)
- Unmounting = Demolishing (component disappears)
Here's how useEffect works with each:
function App() {
useEffect(() => {
console.log('🏗️ House built! (Mounted)');
return () => {
console.log('🏚️ House demolished! (Unmounted)');
};
}, []); // Empty array = only on mount/unmount
useEffect(() => {
console.log('🎨 Redecorating! (Re-rendered)');
}); // No array = every render
return <div>Hello!</div>;
}
When Effects Actually Run
Here's what happens in real life:
function RoomLight({ isOn }) {
// 1. Runs after mount AND when isOn changes
useEffect(() => {
console.log(`Light turned ${isOn ? 'on' : 'off'}`);
}, [isOn]);
// 2. Runs after EVERY render
useEffect(() => {
console.log('Room redecorated');
}); // No dependency array
// 3. Runs ONCE after mount
useEffect(() => {
console.log('Room built');
}, []); // Empty dependency array
return <div>Room</div>;
}
Cleanup (The Important Part)
Here's when you need cleanup:
- Timers
- Subscriptions
- Event listeners
- WebSocket connections
function Timer() {
useEffect(() => {
// Set up
const timer = setInterval(() => {
console.log('Tick');
}, 1000);
// Clean up
return () => {
clearInterval(timer); // Prevents memory leaks
};
}, []); // Only on mount
}
function WindowWidth() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
// Set up
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener('resize', handleResize);
// Clean up
return () => {
window.removeEventListener('resize', handleResize);
};
}, []); // Only on mount
}
Common Gotchas I Hit
1. Missing Dependencies
function Counter({ start }) {
const [count, setCount] = useState(start);
// Wrong - doesn't update when start changes
useEffect(() => {
setCount(start);
}, []); // Lint will warn you
// Right - updates when start changes
useEffect(() => {
setCount(start);
}, [start]);
}
2. Running Too Often
function Profile({ user }) {
// Bad - new object every render
useEffect(() => {
console.log('Profile updated');
}, [{ name: user.name }]); // Runs every time!
// Good - only when name changes
useEffect(() => {
console.log('Profile updated');
}, [user.name]);
}
Quick Tips
- The cleanup function runs before the effect runs again
- Dependencies should include everything that changes
- Empty array = mount/unmount only
- No array = every render
- When in doubt, let the linter guide you
Mental Model
Think of it this way:
useEffect(() => {
// This runs AFTER render
console.log('Effect happened');
return () => {
// This runs BEFORE next effect or unmount
console.log('Cleanup happened');
};
}, [/* dependencies change = effect runs again */]);
That's useEffect stripped down to what matters. Still confused about something? Drop a comment - I love debugging these things.
Follow for more React tips from the trenches 🎯
This content originally appeared on DEV Community and was authored by Marvin Roque

Marvin Roque | Sciencx (2025-02-18T02:52:49+00:00) useEffect: Side Effects in React. Retrieved from https://www.scien.cx/2025/02/18/useeffect-side-effects-in-react/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.