useEffect Made Easy: A Beginner's Handbook

When building React appplications, managing side-effect is crucial - whether it’s fetching data, subscribing to a Websocket or updating the DOM manually. That's where React’s useEffect hook comes into play.

In this blog, we will dive into the useEffect hook, understand its syntax, how it works, how and when to use it, dependency array, cleanup function, side effects and so on. By the end, you will have a nice understanding of useEffect hook.

📌 What is useEffect?

useEffect in simple terms is:

A React hook used to handle side effects in functional component.

So, what is side effect then? Well, side effect is any logic that interacts with the outside world (outside the React component’s render cycle) or occurs after the component render. Here are some examples:

  • Data fetching (APIs, Databases)

  • Manual DOM manipulation

  • Timers (setTimeout, setInterval)

  • Subscriptions (event listeners, Websocket connections)

  • Logging or analytics tracking

In React terms:

A side effect is something that runs after your component renders and affects or depends on things outside the component’s pure logic.

⚙️ Syntax

import { useEffect } from 'react'

useEffect(() => {
  // Side effect code runs here (after render)
  return () => {
    // Cleanup code (runs before re-run or unmount)
  };
}, [dependencies]); // Dependency array controls when the effect runs

🛠️ When does useEffect run?

The useEffect hook in React runs at specific times during a component's lifecycle, primarily after rendering and in response to dependency changes. Here’s a detailed breakdown of when it executes:

  1. After every render (no dependency array)

     useEffect(() => {
       // runs after every render (initial + updates)
     });
    

    When: After the component mounts and after every re-render (initial and subsequent updates)

    Use Case: Rarely needed. Avoid unless you have a specific reason.

    Risk: Can cause infinite loops if the effect updates state unconditionally.

  2. Only After Initial Render (Empty dependency Array)

useEffect(() => {
  // Runs ONLY after the initial render (like `componentDidMount`)
}, []);
  • When: Once, when the component mounts (initial render)

  • Use Case: Initial setup (e.g., fetching data, setting up subscriptions).

  • Cleanup: Runs when the component unmounts (if a cleanup function is returned).

  1. When dependencies change

useEffect(() => {
  // Runs after initial render AND when `dep1` or `dep2` change
}, [dep1, dep2]);
  • When: Initial render and whenever any dependency in the array changes.

  • Use Case: Synchronizing with external data (e.g., re-fetching when userId changes).

  • Cleanup: Runs before the effect re-executes or on unmount.

  1. Cleanup Phase

useEffect(() => {
  // Side effect (e.g., subscribe to events)
  return () => {
    // Cleanup (e.g., unsubscribe)
  };
}, [deps]);
  • When

    • Before re-running the effect

    • Before unmounting the component

  • Use Case: Preventing memory leaks (e.g., clearing timers, unsubscribing)

❌ Common mistakes to avoid

  1. Missing dependencies

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

useEffect(() => {
  const timer = setInterval(() => {
    console.log(count); // Always logs 0 (stale closure)
  }, 1000);
  return () => clearInterval(timer);
}, []); // Missing `count` dependency

Problem: Count is stuck at 0 forever even if we update count with setCount.

How to fix ✅?

  • Add count as dependency
useEffect(() => {
  const timer = setInterval(() => {
    console.log(count);
  }, 1000);
  return () => clearInterval(timer);
}, [count]);
  1. Infinite Loop

const [value, setValue] = useState(0);

useEffect(() => {
  setValue(value + 1); // Re-runs effect forever
}); // No dependency array

Problem: The effect updates state, triggering itself endlessly.

Fix: Add empty array to run once.

}, []); // ✅ Runs only on mount
  1. Forgotten cleanup

useEffect(() => {
  window.addEventListener('resize', handleResize);
  // Missing cleanup ➜ crash if handleResize runs after unmount
}, []);

Problem: Not cleaning up subscriptions/timers can lead to memory leaks.

Fix: Always return a cleanup.

return () => window.removeEventListener('resize', handleResize); // ✅

🧩 Final Analogy: useEffect as a Room Service

Imagine your React component is a hotel guest:

  • useEffect is like room service that comes after you've checked in (render).

  • If your room condition (state/props) changes, room service re-visits.

  • When you check out (unmount), they clean the room (cleanup function).

🧠 Conclusion

Understanding the useEffect hook is crucial for managing side effects in React applications. By knowing when and how it runs, you can efficiently handle tasks like data fetching and subscriptions.

Pay attention to the dependency array and include cleanup functions to prevent memory leaks. Avoid common mistakes such as missing dependencies and infinite loops to keep your components performant and reliable. With these insights, you're ready to effectively use useEffect in your projects.

0
Subscribe to my newsletter

Read articles from Raj Kiran Chaudhary directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Raj Kiran  Chaudhary
Raj Kiran Chaudhary

Frontend developer who weaves creativity and code, book lover, avid traveller and a tech enthusiast