useEffect Hook

Loading concept...

The useEffect Hook: Your Component’s Helpful Robot Assistant πŸ€–

Imagine you have a robot helper in your room. This robot watches what you do and responds to help you. When you wake up, it opens the curtains. When you leave, it turns off the lights. When you come back with new toys, it rearranges the shelf.

That’s exactly what useEffect does for your React components!


What is useEffect?

Think of your React component as a living room. The component renders (like decorating the room), and sometimes you need side effects β€” things that happen outside the room but are triggered by changes inside it.

Side effects include:

  • πŸ“‘ Fetching data from the internet
  • ⏰ Setting up timers
  • πŸ“ Updating the browser’s title
  • πŸ”Œ Connecting to external services
import { useEffect } from 'react';

function MyComponent() {
  useEffect(() => {
    // This is your robot's task!
    document.title = "Hello!";
  });

  return <h1>Welcome</h1>;
}

The robot runs its task after your room is decorated (after render).


useEffect Basics

The simplest useEffect runs after every render:

useEffect(() => {
  console.log("Component rendered!");
});

Think of it like this:

  1. 🎨 React paints your component
  2. πŸ€– Robot wakes up and does its job
  3. βœ… Done!

The Three Parts of useEffect

useEffect(() => {
  // 1. SETUP: What to do
  const timer = setInterval(tick, 1000);

  // 2. CLEANUP: How to undo it
  return () => clearInterval(timer);

}, [dependencies]); // 3. WHEN to react

Effect Dependencies

The dependency array tells your robot: β€œOnly work when these things change!”

No Array = Run Every Time

useEffect(() => {
  console.log("I run after EVERY render");
});
// Robot works constantly! 😰

Empty Array = Run Once

useEffect(() => {
  console.log("I run ONCE when born");
}, []);
// Robot works only on first day! πŸŽ‰

With Dependencies = Run When They Change

useEffect(() => {
  console.log(`Count is now ${count}`);
}, [count]);
// Robot works only when count changes! 🎯

Real Example:

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    // Fetch new user when userId changes
    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(data => setUser(data));
  }, [userId]); // πŸ‘ˆ Only re-run when userId changes

  return <div>{user?.name}</div>;
}

Effect Cleanup

Your robot is polite! Before leaving or doing a new task, it cleans up the old mess.

Why Cleanup?

Imagine you start a timer. If you leave without stopping it, it keeps ticking forever β€” wasting energy and causing bugs!

useEffect(() => {
  // SETUP: Start a subscription
  const subscription = api.subscribe(userId);

  // CLEANUP: Cancel when leaving
  return () => {
    subscription.unsubscribe();
  };
}, [userId]);

When Does Cleanup Run?

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ 1. Component mounts             β”‚
β”‚    β†’ Effect runs (setup)        β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ 2. Dependency changes           β”‚
β”‚    β†’ Cleanup runs (old effect)  β”‚
β”‚    β†’ Effect runs (new setup)    β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ 3. Component unmounts           β”‚
β”‚    β†’ Cleanup runs (final)       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Timer Example:

function Clock() {
  const [time, setTime] = useState(new Date());

  useEffect(() => {
    const id = setInterval(() => {
      setTime(new Date());
    }, 1000);

    // Cleanup stops the timer!
    return () => clearInterval(id);
  }, []);

  return <p>{time.toLocaleTimeString()}</p>;
}

Effect Remounting

In React’s Strict Mode (development), your component mounts twice on purpose. This helps catch bugs!

Normal:     Mount β†’ Effect runs
Strict:     Mount β†’ Effect β†’ Unmount β†’ Mount β†’ Effect

Why? React wants to test if your cleanup works properly.

The Test

useEffect(() => {
  const connection = createConnection();
  connection.connect();

  return () => connection.disconnect();
}, []);

Good code: Connects β†’ Disconnects β†’ Connects (works!) Bad code: Connects β†’ Nothing β†’ Connects (two connections! πŸ’₯)

If your effect breaks with remounting, you have a cleanup bug!


When NOT to Use Effects

Stop! πŸ›‘ Not everything needs useEffect. Many developers overuse it!

❌ DON’T: Transform Data for Rendering

// BAD ❌
const [filtered, setFiltered] = useState([]);
useEffect(() => {
  setFiltered(items.filter(x => x.active));
}, [items]);

// GOOD βœ… (Calculate during render!)
const filtered = items.filter(x => x.active);

❌ DON’T: Handle User Events

// BAD ❌
useEffect(() => {
  if (submitted) {
    sendData();
  }
}, [submitted]);

// GOOD βœ… (Handle in the event!)
function handleSubmit() {
  sendData();
}

βœ… DO Use Effects For:

  • 🌐 Fetching data from APIs
  • πŸ”Œ Subscribing to external events
  • ⏰ Setting up timers
  • πŸ“Š Analytics tracking
  • 🎨 Syncing with non-React widgets

Flow Chart

graph TD A[Need a side effect?] --> B{Is it during render?} B -->|Yes| C[Calculate inline - no effect!] B -->|No| D{Triggered by event?} D -->|Yes| E[Put in event handler!] D -->|No| F[Use useEffect βœ…]

Removing Dependencies

Sometimes your effect has too many dependencies. Here’s how to trim them!

Problem: Updating State Based on Previous

// BAD: count is a dependency
useEffect(() => {
  const id = setInterval(() => {
    setCount(count + 1); // πŸ‘ˆ Needs count!
  }, 1000);
  return () => clearInterval(id);
}, [count]); // Restarts every second! 😱

Solution: Use Functional Updates

// GOOD: No dependency on count!
useEffect(() => {
  const id = setInterval(() => {
    setCount(c => c + 1); // πŸ‘ˆ Uses previous value
  }, 1000);
  return () => clearInterval(id);
}, []); // Runs once! πŸŽ‰

Moving Functions Inside Effects

// BAD: fetchData changes every render
function fetchData() {
  fetch(`/api/${userId}`);
}

useEffect(() => {
  fetchData();
}, [fetchData]); // Runs too often!

// GOOD: Define inside effect
useEffect(() => {
  function fetchData() {
    fetch(`/api/${userId}`);
  }
  fetchData();
}, [userId]); // Only runs when userId changes!

Stale Closures

A stale closure is when your effect remembers old values instead of current ones. It’s like your robot using an outdated shopping list!

The Problem

function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const id = setInterval(() => {
      console.log(count); // Always logs 0! 😱
    }, 1000);
    return () => clearInterval(id);
  }, []); // πŸ‘ˆ Empty array = captures initial count

  return (
    <button onClick={() => setCount(c => c + 1)}>
      Count: {count}
    </button>
  );
}

Why? The effect β€œcloses over” the initial count (0) and never updates.

Solution 1: Add Dependency

useEffect(() => {
  const id = setInterval(() => {
    console.log(count); // βœ… Always current!
  }, 1000);
  return () => clearInterval(id);
}, [count]); // πŸ‘ˆ Re-runs when count changes

Solution 2: Use Ref for Latest Value

function Counter() {
  const [count, setCount] = useState(0);
  const countRef = useRef(count);

  // Keep ref in sync
  useEffect(() => {
    countRef.current = count;
  }, [count]);

  useEffect(() => {
    const id = setInterval(() => {
      console.log(countRef.current); // βœ… Always current!
    }, 1000);
    return () => clearInterval(id);
  }, []);

  return <button onClick={() => setCount(c => c + 1)}>
    Count: {count}
  </button>;
}

Stale Closure Flow

graph TD A[Effect Created] --> B[Captures count=0] B --> C[Interval Runs] C --> D{count updated?} D -->|Yes| E[count=5 in state] D -->|No| C E --> F[But effect still sees 0!] F --> G[STALE CLOSURE! πŸ’€]

Quick Reference Summary

Concept What It Means
Basics Code that runs after render
Dependencies Control when effect re-runs
Cleanup Undo your effect before next run
Remounting Strict Mode tests your cleanup
Not for effects Calculations & event handlers
Removing deps Use functional updates
Stale closures Old values trapped in effects

The Golden Rules πŸ†

  1. Effects are for syncing with external systems, not internal calculations
  2. Always clean up subscriptions, timers, and connections
  3. Keep dependencies honest β€” include what you use
  4. Use functional updates to reduce dependencies
  5. Watch for stale closures β€” your effect might be reading old data!

Your robot helper is powerful, but only if you give it clear instructions! πŸ€–βœ¨

Loading story...

No Story Available

This concept doesn't have a story yet.

Story Preview

Story - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.

Interactive Preview

Interactive - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.

No Interactive Content

This concept doesn't have interactive content yet.

Cheatsheet Preview

Cheatsheet - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.

No Cheatsheet Available

This concept doesn't have a cheatsheet yet.

Quiz Preview

Quiz - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.

No Quiz Available

This concept doesn't have a quiz yet.