DEV Community

Cover image for 🔍 React useEffect Demystified: A Beginner’s Guide
Srushti Patil
Srushti Patil

Posted on

🔍 React useEffect Demystified: A Beginner’s Guide

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:

  1. Fetching data from an API
  2. Setting up event listeners
  3. Starting/stopping a timer
  4. Updating the document title

⚡ Meet useEffect

Here’s the syntax:

useEffect(() => {
  // side effect code here
  return () => {
    // cleanup code here (optional)
  };
}, [dependencies]);
Enter fullscreen mode Exit fullscreen mode
  • 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>
  );
}
Enter fullscreen mode Exit fullscreen mode

🔄 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!");
});

Enter fullscreen mode Exit fullscreen mode

📌 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).");
}, []);

Enter fullscreen mode Exit fullscreen mode

📞 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]);

Enter fullscreen mode Exit fullscreen mode

📌 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]);

Enter fullscreen mode Exit fullscreen mode

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]);

Enter fullscreen mode Exit fullscreen mode

📌 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)

Collapse
 
snehal_p profile image
Snehal Pande

Nicely explained 🙌