When you start with React, useState feels simple — it stores values and re-renders your component.
But then comes useEffect… and suddenly, beginners ask:
- “Why is my code running twice?”
- “Why do I need cleanup?”
- “What’s this dependency array thing?”
Let’s break it down step by step with real-life analogies!
🏠 What is a Side Effect?
In programming, a side effect is something that happens outside the main flow of your code.
👉 Think of it like this:
You enter your room → the light automatically turns on.
You leave the room → the light turns off.
The room is your component.
The light is a side effect.
It happens because of your presence, but it’s not the main job of being in the room.
In React, these side effects could be:
- Fetching data from an API
- Setting up event listeners
- Starting/stopping a timer
- Updating the document title
⚡ Meet useEffect
Here’s the syntax:
useEffect(() => {
// side effect code here
return () => {
// cleanup code here (optional)
};
}, [dependencies]);
- The first function is what runs as the side effect.
- The return function is the cleanup (think of it as “turning off the light when you leave”).
- The [dependencies] array tells React when to run the effect.
📦 Real-Life Example: Online Shopping
You add a product to your cart → instantly, you get an order confirmation email.
The cart = State
The email = Side effect
import { useState, useEffect } from "react";
function Cart() {
const [items, setItems] = useState([]);
useEffect(() => {
console.log("📧 Sending confirmation email…");
}, [items]); // runs whenever items change
return (
<div>
<button onClick={() => setItems([...items, "Shoe"])}>Add Shoe</button>
<p>Items: {items.join(", ")}</p>
</div>
);
}
🔄 Dependency Array in useEffect
🧃 Example 1: No Dependency Array → Runs on Every Render
Imagine you open a fridge — every time you even peek inside, the fridge alarm beeps. Annoying, right?
useEffect(() => {
console.log("Effect ran!");
});
📌 This runs every single render, which can cause unnecessary work.
📦 Example 2: Empty Array [] → Runs Only Once
Like a fridge alarm that only beeps once when you first open it.
useEffect(() => {
console.log("Effect ran only once (on mount).");
}, []);
📞 Example 3: With Dependencies [count] → Runs When Count Changes
Think of it like a reminder that only triggers when a specific condition changes.
useEffect(() => {
console.log("Effect ran because count changed:", count);
}, [count]);
📌 Effect re-runs only when the count changes.
🧹 Why Cleanup Functions Matter
Cleanup = “turning things off” before setting them up again.
Without cleanup, you can end up with duplicate event listeners, memory leaks, or multiple timers running at once.
🎵 Example 4: Timer Without Cleanup (Buggy)
useEffect(() => {
const interval = setInterval(() => {
console.log("⏰ Timer tick...");
}, 1000);
}, [count]);
Every time the count changes, a new interval starts.
Old intervals are never cleared → multiple timers run together → app lags.
✅ Example 5: Timer With Cleanup (Correct)
useEffect(() => {
const interval = setInterval(() => {
console.log("⏰ Timer tick...");
}, 1000);
return () => {
clearInterval(interval); // cleanup old timer
console.log("🧹 Timer cleaned up");
};
}, [count]);
📌 Now, whenever the count changes:
Old timer is cleared. The new timer starts fresh.
Just like turning off the old alarm before setting a new one.
Top comments (1)
Nicely explained 🙌