Skip to Content
Module 5: Side Effects & Hooks5.2 useEffect Masterclass

Module 5.2: useEffect Masterclass

A banner displaying the title "useEffect Masterclass" with a React logo and a clock icon, symbolizing time-based effects.

🎯 Learning Objectives

After this lesson, you’ll master:

  • Dependency Array: Gain a deep understanding of the differences between no dependencies, an empty dependency array [], and an array with dependencies [a, b].
  • Advanced Cleanup Function: Learn to handle unregistering global events (Window/Document Events).
  • Data Fetching: Explore the basic function pattern for calling APIs inside useEffect and updating state.
  • Race Conditions: Recognize the issues that arise from making continuous API calls.

1. Deep Dive: Dependency Array

The Dependency Array is useEffect’s second argument. It determines WHEN your effect re-runs.

A flowchart illustrating the useEffect dependency flow: Effect runs on mount. If dependencies change, effect re-runs. If dependencies are empty, it runs once. If no dependencies, it runs on every render.

1.1. Detailed Comparison

CodeMeaningBehavior
useEffect(fn)No ArrayRuns after EVERY render.
⚠️ Dangerous: Easily causes an Infinite Loop if you set state inside.
useEffect(fn, [])Empty ArrayRuns ONLY ONCE after mounting.
✅ Perfect for: Initial API calls, Event Listeners.
useEffect(fn, [a, b])With VariablesRuns initially AND whenever a or b’s value changes.
✅ Ideal for: Synchronizing logic (e.g., search keyword changes -> re-call API).

1.2. Infinite Loop Trap

A classic pitfall: Setting a dependency on a state that the effect itself is modifying.

// ❌ INCORRECT: Creates an infinite loop useEffect(() => { setCount(count + 1); // 1. Changes count -> Triggers Re-render }, [count]); // 2. Count changes -> Triggers Effect -> Back to 1

How to Fix: Use a functional update or remove the dependency if it’s not needed.

// ✅ CORRECT useEffect(() => { const timer = setInterval(() => { setCount((prev) => prev + 1); // Doesn't need to depend on the current 'count' }, 1000); return () => clearInterval(timer); }, []); // Only runs once to set up the timer

2. Cleanup Function: Global Events

When you subscribe to events on the window (e.g., resize, scroll, keydown), you MUST remove the listener when the component unmounts. Otherwise, the event will duplicate every time the component re-renders, leading to memory leaks and unexpected behavior.

Standard Procedure:

  1. Write the event handler function.
  2. window.addEventListener in the effect body (Mount).
  3. window.removeEventListener in the return function (Unmount).
useEffect(() => { // 1. Define handler const handleResize = () => { console.log(window.innerWidth); }; // 2. Add listener (Mount) window.addEventListener('resize', handleResize); // 3. Cleanup (Unmount) return () => { window.removeEventListener('resize', handleResize); }; }, []);

3. Data Fetching (Calling API)

In vanilla React (without libraries like React Query), we call APIs inside useEffect and save the results to state.

A flowchart showing the process of API data fetching: useEffect triggers fetchData. fetchData sets loading true, calls API, handles response (success/error), updates state with data/error, and finally sets loading false.

Standard Code Example

import { useState, useEffect } from 'react'; interface User { id: number; name: string; } function UserList() { const [users, setUsers] = useState<User[]>([]); const [loading, setLoading] = useState<boolean>(true); const [error, setError] = useState<string | null>(null); useEffect(() => { // 1. Create an async function inside the effect const fetchData = async () => { try { setLoading(true); const response = await fetch('https://jsonplaceholder.typicode.com/users'); if (!response.ok) throw new Error('Network error'); const data: User[] = await response.json(); setUsers(data); // Update data } catch (err: any) { setError(err.message); // Handle error } finally { setLoading(false); // Stop loading } }; // 2. Call the function fetchData(); }, []); // [] -> Only calls once // You might want to include dependencies here if you need to refetch based on props or state changes if (loading) return <p>Loading...</p>; if (error) return <p>Error: {error}</p>; return ( <ul> {users.map((user) => ( <li key={user.id}>{user.name}</li> ))} </ul> ); }

4. Labs

Lab 1: Active Window Listener (The “Responsive” Spy)

Instead of just displaying boring numbers, let’s create a “Floating Debug Widget” with a Glassmorphism style. This widget will change color based on the screen size (Mobile/Tablet/Desktop).

A "Window Tracker" debug widget with glassmorphism style, displaying current window width and height. The widget changes background color based on screen size, indicating mobile, tablet, or desktop.

💡 Hint:

  • Use window.addEventListener('resize') to update the width/height state.
  • Mandatory: Include removeEventListener in the cleanup function to prevent memory leaks.
  • Use if/else logic or a ternary operator to change the background color (bg-red-500, bg-green-500) according to breakpoints (e.g., < 640px for mobile).

Lab 2: English Dictionary (Debounce & API)

Build a sophisticated English-English dictionary.

  • Features: Look up vocabulary, pronunciation (Audio), display word types (noun, verb), and examples.
  • Techniques: useEffect with Debounce to search when the user stops typing, handle Audio API.

A screenshot of a dictionary app interface showing search results for "hello". It displays pronunciation, word type (interjection, noun, verb), and example sentences.

💡 Hint:

  • Free API: https://api.dictionaryapi.dev/api/v2/entries/en/<word>
  • Implement search Debounce (600ms) to avoid spamming the API on every keystroke.
  • Professionally display Loading (Skeleton) and Not Found (404) states.

5. Summary

  1. The Dependency Array is the key to controlling the effect’s lifecycle.
  2. The Cleanup Function is where you clean up “junk” (event listeners, timers) to protect memory.
  3. When calling an API in useEffect:
    • Create an async function inside the effect.
    • Always handle 3 states: loading, data, error.
    • Pay attention to using Debounce (setTimeout/clearTimeout) when calling an API based on keystroke events.

6. References

Last updated on