Why useEffect Exists

React's rendering must be pure—meaning it shouldn't modify the DOM, fetch data, or perform side effects during render. Instead, useEffect lets us handle side effects after the component has been painted.


What are Side Effects?

Side effects are operations that interact with outside elements (API calls, event listeners, timers, etc.). Without useEffect, we'd struggle to perform these tasks in function components.

Core Idea

  • useEffect runs after the render is committed to the DOM.
  • It helps manage side effects in React.
  • We can control when the effect runs using dependencies.

1️⃣ Basic Syntax of useEffect

useEffect(() => {
  // Side effect logic (e.g., API call, event listener)

  return () => {
    // Cleanup function (optional)
  };
}, [dependencies]); // Dependency array

Breakdown

✅ First argument → Function containing the effect.
✅ Second argument → Dependency array (controls when it runs).
✅ Return function → Cleanup function (runs before next effect execution or unmounting).


2️⃣ Different Ways to Use useEffect

1️⃣ No Dependency Array (Runs on Every Render)

useEffect(() => {
  console.log("Runs after every render");
});
  • Re-runs after every render (not recommended in most cases).
  • Can cause infinite loops if it modifies state inside.

2️⃣ Empty Dependency Array (Runs Only Once)

useEffect(() => {
  console.log("Runs only on mount!");
}, []); // Empty dependency array
  • Runs only once when the component mounts.
  • Similar to componentDidMount in class components.
  • Best for fetching data or setting up subscriptions.

3️⃣ Dependency Array (Runs on State or Prop Changes)

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

useEffect(() => {
  console.log(`Count changed: ${count}`);
}, [count]); // Runs whenever `count` changes
  • The effect re-runs whenever any dependency in the array changes.
  • Essential for reactive behaviors based on state/props.

4️⃣ Cleanup Function (For Unmounting & Re-running Effects)

useEffect(() => {
    const controller = new AbortController();

    fetch("https://url", { signal: controller.signal })
      .then((res) => res.json())

    return () => controller.abort(); // Cleanup: Abort fetch on unmount
  }, []);

3️⃣ Cleanup Matters

Cleanup function runs:

  1. Before running the effect again (if dependencies change).
    • Whenever the dependencies in the dependency array change, React first cleans up the previous effect before running the new one.
  2. When the component unmounts (to prevent memory leaks).
    • If an effect sets up an event listener or subscribes to a service, it should clean up when the component unmounts, or it could cause memory leaks.

If an effect isn't cleaned up properly, React may:
❌ Leak memory (e.g., event listeners that are never removed).
❌ Cause race conditions (outdated API requests could still resolve, updating unmounted components and causing errors.).
❌ Trigger unexpected behavior when dependencies change.


4️⃣ ⚠️ Common Mistakes & Gotchas

❌ Async Functions Directly in useEffect
React expects useEffect to return either:

  • Nothing (undefined)
  • A cleanup function

But if you use async directly, it always returns a Promise → React doesn’t expect that.

// ❌ Incorrect way
useEffect(async () => {
  const data = await fetchData();
}, []);

🚨 This will cause a warning:

"Effect callbacks are synchronous to prevent race conditions. Put the async function inside the effect."

✅ Proper Way
Wrap the async function inside useEffect:

useEffect(() => {
  const fetchData = async () => {
    const data = await fetch(...);
    setData(data);
  };

  fetchData();
}, []);

5️⃣ Rules of useEffect (and all Hooks) 📌

  1. Always call hooks at the top level of your component.
  2. Do NOT call hooks inside loops, conditions, or nested functions.
  3. Every variable used inside useEffect must be in the dependency array (or use useRef to persist values without re-running effects).
  4. Effects should be as specific as possible (avoid unnecessary re-renders).

🔥 Final Takeaway

🚀 useEffect is one of the most powerful hooks in React.
📌 It helps manage side effects (API calls, subscriptions, DOM interactions).
⚡ Control when it runs using the dependency array.
🧹 Always clean up effects to avoid memory leaks.

💬 Have questions or tips on using useEffect? Drop them in the comments below! 👇