DEV Community

Cover image for Day 4 of #100DaysOfCode — Mastering useEffect in React
M Saad Ahmad
M Saad Ahmad

Posted on

Day 4 of #100DaysOfCode — Mastering useEffect in React

Understanding useEffect in React is like unlocking a magical spellbook, suddenly you can summon APIs, tame event listeners, and command the DOM.
It’s the key to handling side effects, keeping your UI consistent, and writing logic that reacts to data changes.

In today’s challenge, I dug deep into useEffect: why it exists, how it works, and how to avoid the dreaded infinite loops.
Here’s a clear and practical breakdown.


What Are Side Effects in React?

Side effects are any actions your component performs outside the normal rendering process.
React’s render cycle must remain predictable and pure—but many real tasks aren’t pure at all, such as:

  • Fetching data from an API
  • Updating document.title
  • Working with timers (setTimeout, setInterval)
  • Using browser APIs like localStorage
  • Adding event listeners
  • Subscribing to WebSockets

These actions affect the world outside your component—those are side effects.


🤔 Why Do React Components Even Need useEffect?

Isn’t useState enough?

useState updates your UI.
useEffect handles everything outside your UI.

If React allowed side effects during rendering, you’d get unpredictable behavior and potentially infinite loops. React must render → compare → update in a pure, deterministic way.

useEffect runs after React paints the screen, keeping the render pure and safe.


Three Types of useEffect Behavior

1️⃣ No Dependency Array: Runs on every render

useEffect(() => {
  console.log("I run after every render");
});
Enter fullscreen mode Exit fullscreen mode

When to use:

Rarely.
This triggers on initial mount + every re-render, which can cause performance issues or infinite loops.


2️⃣ Empty Dependency Array []: Runs only once (on mount)

useEffect(() => {
  console.log("I run only once when the component mounts");
}, []);
Enter fullscreen mode Exit fullscreen mode

When to use:

  • Fetch API data on mount
  • Setup once (listeners, subscriptions, timers)
  • Initialize state from localStorage

This makes the effect behave like:
componentDidMount in class components.


3️⃣ Dependency-Based: Runs when dependencies change

useEffect(() => {
  console.log("I run when count changes");
}, [count]);
Enter fullscreen mode Exit fullscreen mode

When to use:

  • Syncing state with props
  • Triggering re-fetching when filters change
  • Updating UI or document title
  • Running logic when some value updates

This is the most common and most powerful usage.


How Does the Dependency Array Actually Work?

The array tells React:

“Run this effect only if any of these values change from the previous render.”

React compares each dependency with its previous value using shallow comparison.

Example:

useEffect(() => {
  console.log("Runs when userId changes");
}, [userId]);
Enter fullscreen mode Exit fullscreen mode

If userId goes from 12, the effect runs.

Important:

  • Objects, arrays, and functions always change reference unless memoized
  • This can cause unintentionally frequent effect runs

How to Avoid Infinite Loops in useEffect

The most common cause:

useEffect(() => {
  setCount(count + 1);
}, [count]);
Enter fullscreen mode Exit fullscreen mode

What happens?

  • Setting state triggers re-render
  • Dependencies detect change
  • Effect runs again
  • State updates
  • Loop forever

How to prevent:

  • Avoid updating state inside the effect based on its own dependency
  • Use functional updates instead:
setCount(prev => prev + 1);
Enter fullscreen mode Exit fullscreen mode
  • Or restructure logic to avoid state → effect → state loops.

Cleanup Functions: What They Are and Why We Need Them

Cleanup functions run before the effect runs again or when the component unmounts.

Syntax:

useEffect(() => {
  console.log("Effect started");

  return () => {
    console.log("Cleanup before re-run or unmount");
  };
}, []);
Enter fullscreen mode Exit fullscreen mode

Used for cleaning:

  • Event listeners
  • Subscriptions (WebSockets, Firebase, etc.)
  • Intervals and timeouts
  • Removing observers
  • Aborting fetch requests

Example (listener cleanup):

useEffect(() => {
  const handleResize = () => console.log("Resized");

  window.addEventListener("resize", handleResize);

  return () => {
    window.removeEventListener("resize", handleResize);
  };
}, []);
Enter fullscreen mode Exit fullscreen mode

Without cleanup → memory leaks.


Real-World Use Cases of useEffect


1. Fetching API Data

useEffect(() => {
  async function loadData() {
    const res = await fetch("https://api.example.com/data");
    const data = await res.json();
    setItems(data);
  }
  loadData();
}, []);
Enter fullscreen mode Exit fullscreen mode

2. Updating Document Title

useEffect(() => {
  document.title = `Count: ${count}`;
}, [count]);
Enter fullscreen mode Exit fullscreen mode

3. Timers

useEffect(() => {
  const timer = setInterval(() => {
    console.log("Tick");
  }, 1000);

  return () => clearInterval(timer);
}, []);
Enter fullscreen mode Exit fullscreen mode

4. Adding Event Listeners

useEffect(() => {
  const handler = () => console.log("Clicked");

  window.addEventListener("click", handler);

  return () => window.removeEventListener("click", handler);
}, []);
Enter fullscreen mode Exit fullscreen mode

5. Working with localStorage

Save to storage whenever value changes:

useEffect(() => {
  localStorage.setItem("name", name);
}, [name]);
Enter fullscreen mode Exit fullscreen mode

Load once on mount:

useEffect(() => {
  const saved = localStorage.getItem("name");
  if (saved) setName(saved);
}, []);
Enter fullscreen mode Exit fullscreen mode

Summary

Concept Meaning
Side effects Actions outside rendering (API, timers, listeners)
Why useEffect Keeps render pure, runs effects after UI update
No dependency Runs on every render
Empty array Runs once on mount
Dependency array Runs when listed values change
Cleanup Unsubscribes, removes listeners, clears timers
Use cases API calls, title updates, listeners, timers, storage

Final Thoughts

Learning useEffect is a turning point in becoming comfortable with React.
It powers almost all real app functionality—from fetching data to syncing your UI with the outside world.

A deeper comprehension of useEffect supports the creation of components that scale gracefully as application complexity grows.

If you're learning too, feel free to share your thoughts or questions below! 👇

Happy coding!

Top comments (0)