14_useEffect Hook - Maniconserve/React-Wiki GitHub Wiki

What is useEffect?

useEffect lets you run side effects in a functional component. A side effect is anything that happens outside of rendering — fetching data, setting up a timer, updating the document title, adding event listeners, etc.

import { useEffect } from 'react';

useEffect(() => { // side effect code here }, [dependencies]);

Part What it is
First argument A function containing your side effect code
Second argument Dependency array — controls when the effect runs

What is useEffect?

useEffect lets you run side effects in a functional component. A side effect is anything that happens outside of rendering — fetching data, setting up a timer, updating the document title, adding event listeners, etc.

import { useEffect } from 'react';

useEffect(() => {
  // side effect code here
}, [dependencies]);
Part What it is
First argument A function containing your side effect code
Second argument Dependency array — controls when the effect runs

The 3 Ways to Use useEffect

The behaviour of useEffect is entirely controlled by the dependency array — the second argument.


1. Without a Dependency Array

Runs after every single render — the initial render and every re-render after that.

import { useState, useEffect } from 'react';

function App() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('');

  useEffect(() => {
    console.log('Effect ran — component rendered or re-rendered');
  }); // ← no second argument at all

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Count: {count}</button>
      <input value={name} onChange={e => setName(e.target.value)} placeholder="Type name" />
    </div>
  );
}

Every time count changes OR name changes — the effect runs. This is rarely what you want and can cause performance issues.

When to use: Almost never. Usually an oversight. If you find yourself using this, you likely need one of the other two forms.


2. With an Empty Dependency Array []

Runs only once — after the very first render. Never runs again no matter how many times the component re-renders.

import { useState, useEffect } from 'react';

function App() {
  const [user, setUser] = useState(null);

  useEffect(() => {
    console.log('Runs only once — on mount');

    // perfect place for:
    // - fetching initial data from an API
    // - setting up a timer
    // - adding event listeners
    fetch('https://jsonplaceholder.typicode.com/users/1')
      .then(res => res.json())
      .then(data => setUser(data));

  }, []); // ← empty array

  return (
    <div>
      {user ? <p>Hello, {user.name}!</p> : <p>Loading...</p>}
    </div>
  );
}

When to use: Fetching data when the page loads, setting up subscriptions, initialising third-party libraries.

This is the equivalent of componentDidMount in class components.


3. With Dependencies [value]

Runs after the first render and again whenever any value in the dependency array changes.

import { useState, useEffect } from 'react';

function App() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('');

  useEffect(() => {
    console.log('count changed to:', count);
    document.title = `Count is ${count}`;
  }, [count]); // ← runs only when 'count' changes

  // This effect only runs when 'name' changes
  useEffect(() => {
    console.log('name changed to:', name);
  }, [name]); // ← runs only when 'name' changes

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Count: {count}</button>
      <input value={name} onChange={e => setName(e.target.value)} placeholder="Type name" />
    </div>
  );
}
  • Clicking the button changes count → first effect runs, second does not
  • Typing in the input changes name → second effect runs, first does not

When to use: React to a specific state or prop change — refetching data when a search term changes, updating the document title when a value changes, etc.


Comparison — All Three Side by Side

// Runs after EVERY render
useEffect(() => {
  console.log('runs every render');
});

// Runs ONCE after first render only
useEffect(() => {
  console.log('runs once on mount');
}, []);

// Runs on first render + whenever 'count' changes
useEffect(() => {
  console.log('runs when count changes');
}, [count]);
Dependency Array When does it run?
No array After every render
[] empty array Once — after first render only
[value] with values After first render + when any listed value changes

Cleanup Function

Some effects need to be cleaned up when the component is removed from the screen — timers need clearing, event listeners need removing, subscriptions need cancelling. Otherwise they keep running in the background and cause memory leaks.

Return a function from inside useEffect — React runs it when the component unmounts (or before running the effect again).

Example — Clearing a Timer

import { useState, useEffect } from 'react';

function Timer() {
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      setSeconds(prev => prev + 1);
    }, 1000);

    // cleanup — clear the interval when component unmounts
    return () => {
      clearInterval(interval);
      console.log('Timer cleared');
    };

  }, []); // runs once — sets up timer on mount, clears on unmount

  return <p>Seconds: {seconds}</p>;
}

Example — Removing an Event Listener

useEffect(() => {
  function handleResize() {
    console.log('Window width:', window.innerWidth);
  }

  window.addEventListener('resize', handleResize);

  // cleanup — remove listener when component unmounts
  return () => {
    window.removeEventListener('resize', handleResize);
  };

}, []);

Multiple useEffect Calls

You can use useEffect as many times as needed — each handles a separate concern. This keeps related logic together.

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

  // Effect 1 — fetch user whenever userId changes
  useEffect(() => {
    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(data => setUser(data));
  }, [userId]);

  // Effect 2 — fetch posts whenever userId changes
  useEffect(() => {
    fetch(`/api/users/${userId}/posts`)
      .then(res => res.json())
      .then(data => setPosts(data));
  }, [userId]);

  // Effect 3 — update page title whenever user changes
  useEffect(() => {
    if (user) document.title = `${user.name}'s Profile`;
  }, [user]);

  return <div>...</div>;
}

useEffect vs Class Lifecycle Methods

If you have seen class components, useEffect replaces three lifecycle methods:

Class Component useEffect Equivalent
componentDidMount useEffect(() => { }, []) — empty array
componentDidUpdate useEffect(() => { }, [value]) — with dependency
componentWillUnmount return () => { } — cleanup function
// Class component
componentDidMount() {
  fetch('/api/data').then(...);
}
componentWillUnmount() {
  clearInterval(this.timer);
}

// Functional equivalent — both in one useEffect
useEffect(() => {
  fetch('/api/data').then(...);

  return () => clearInterval(timer); // cleanup
}, []);

Quick Reference

Goal Code
Run once on mount useEffect(() => { }, [])
Run on every render useEffect(() => { })
Run when value changes useEffect(() => { }, [value])
Run when multiple values change useEffect(() => { }, [a, b, c])
Cleanup on unmount return () => { cleanup here } inside useEffect

Common Mistakes

// ❌ Missing dependency — effect uses 'count' but doesn't list it
useEffect(() => {
  console.log(count); // reads count but count is not in deps
}, []);

// ✅ Correct — list every value the effect uses
useEffect(() => {
  console.log(count);
}, [count]);


// ❌ Fetching data without [] — causes infinite loop
// setUser triggers a re-render → effect runs again → setUser again → loop
useEffect(() => {
  fetch('/api/user').then(res => res.json()).then(data => setUser(data));
}); // no array!

// ✅ Correct
useEffect(() => {
  fetch('/api/user').then(res => res.json()).then(data => setUser(data));
}, []);

⚠️ **GitHub.com Fallback** ⚠️