React useEffect Hook

Managing side effects in functional components

โšก What is useEffect?

useEffect is a React Hook that lets you perform side effects in functional components. It handles operations like data fetching, subscriptions, timers, and DOM manipulation after rendering completes.


import { useEffect } from 'react';

function Example() {
  useEffect(() => {
    document.title = 'Hello World';
  });
}
                                    

useEffect Key Concepts

๐Ÿ”„

Side Effects

Run code after render

useEffect(() => {
  // Effect code
});
๐Ÿ“‹

Dependencies

Control when effect runs

useEffect(() => {
  // Effect
}, [dependency]);
๐Ÿงน

Cleanup

Clean up resources

useEffect(() => {
  return () => {
    // Cleanup
  };
});
๐ŸŽฏ

Run Once

Execute on mount only

useEffect(() => {
  // Runs once
}, []);

๐Ÿ”น Basic useEffect Example

The simplest useEffect runs after every render. This example updates the document title whenever the component renders, showing how effects execute after the DOM updates.

import { useState, useEffect } from 'react';

function Counter() {
  const [count, setCount] = useState(0);
  
  // Runs after every render
  useEffect(() => {
    document.title = `Count: ${count}`;
  });
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
    </div>
  );
}

Output:

Count: 0

Document title updates with count

๐Ÿ”น useEffect with Dependencies

The dependency array controls when the effect runs. By passing specific values, the effect only executes when those values change, optimizing performance and preventing unnecessary operations.

import { useState, useEffect } from 'react';

function SearchUser() {
  const [userId, setUserId] = useState(1);
  const [user, setUser] = useState(null);
  
  // Runs only when userId changes
  useEffect(() => {
    fetch(`https://api.example.com/users/${userId}`)
      .then(res => res.json())
      .then(data => setUser(data));
  }, [userId]); // Dependency array
  
  return (
    <div>
      <button onClick={() => setUserId(userId + 1)}>
        Next User
      </button>
      <p>User ID: {userId}</p>
    </div>
  );
}

Output:

User ID: 1

Fetches new user when ID changes

๐Ÿ”น useEffect Run Once (Empty Dependencies)

An empty dependency array makes the effect run only once when the component mounts. This is perfect for initial data fetching, setting up subscriptions, or one-time setup operations.

import { useState, useEffect } from 'react';

function UserProfile() {
  const [user, setUser] = useState(null);
  
  // Runs only once on mount
  useEffect(() => {
    fetch('https://api.example.com/user')
      .then(res => res.json())
      .then(data => setUser(data));
  }, []); // Empty array = run once
  
  return (
    <div>
      {user ? (
        <h2>Welcome, {user.name}!</h2>
      ) : (
        <p>Loading...</p>
      )}
    </div>
  );
}

Output:

Loading...

Fetches data once when component loads

๐Ÿ”น useEffect with Cleanup

Return a cleanup function from useEffect to handle resource cleanup. This is essential for subscriptions, timers, and event listeners to prevent memory leaks when the component unmounts.

import { useState, useEffect } from 'react';

function Timer() {
  const [seconds, setSeconds] = useState(0);
  
  useEffect(() => {
    // Set up interval
    const interval = setInterval(() => {
      setSeconds(s => s + 1);
    }, 1000);
    
    // Cleanup function
    return () => {
      clearInterval(interval);
      console.log('Timer cleaned up');
    };
  }, []); // Run once on mount
  
  return (
    <div>
      <p>Timer: {seconds} seconds</p>
    </div>
  );
}

Output:

Timer: 0 seconds

Timer increments every second, cleans up on unmount

๐Ÿ”น Multiple useEffect Hooks

You can use multiple useEffect hooks in one component to separate different concerns. Each effect can have its own dependencies and cleanup logic, making code more organized and maintainable.

import { useState, useEffect } from 'react';

function Dashboard() {
  const [user, setUser] = useState(null);
  const [posts, setPosts] = useState([]);
  
  // Effect 1: Fetch user data
  useEffect(() => {
    fetch('/api/user')
      .then(res => res.json())
      .then(data => setUser(data));
  }, []);
  
  // Effect 2: Fetch posts
  useEffect(() => {
    fetch('/api/posts')
      .then(res => res.json())
      .then(data => setPosts(data));
  }, []);
  
  // Effect 3: Update document title
  useEffect(() => {
    if (user) {
      document.title = `${user.name}'s Dashboard`;
    }
  }, [user]);
  
  return <div>Dashboard Content</div>;
}

Output:

Dashboard Content

Three separate effects handle different tasks

๐Ÿ”น Common useEffect Patterns

๐Ÿ”ธ Data Fetching

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

๐Ÿ”ธ Event Listeners

useEffect(() => {
  const handleResize = () => setWidth(window.innerWidth);
  window.addEventListener('resize', handleResize);
  return () => window.removeEventListener('resize', handleResize);
}, []);

๐Ÿ”ธ Subscriptions

useEffect(() => {
  const subscription = dataSource.subscribe();
  return () => subscription.unsubscribe();
}, []);

๐Ÿ”น useEffect Best Practices

โœ… Do's:

  • Always specify dependencies: Include all values used in effect
  • Clean up side effects: Return cleanup function when needed
  • Separate concerns: Use multiple effects for different tasks
  • Handle async properly: Don't make useEffect callback async

โŒ Don'ts:

  • Don't omit dependencies: Always include what you use
  • Don't mutate state directly: Use setter functions
  • Don't forget cleanup: Clear timers, subscriptions, listeners
  • Don't overuse: Not all logic needs useEffect

๐Ÿง  Test Your Knowledge

When does useEffect with empty dependency array run?