The Complete Guide to the useEffect Hook in React

The Complete Guide to the useEffect Hook in React

A comprehensive guide on using the React useEffect hook, explaining the four primary usage patterns for managing side effects with and without a dependency array. Learn how to control when your effects run and how to use cleanup functions to prevent memory leaks.

frontend
August 31, 2025
8 min read

The Complete Guide to the `useEffect` Hook in React

The useEffect hook is a powerful tool in React that lets you manage "side effects" in your components. Side effects are actions that happen outside of a component's normal rendering process, like fetching data from an API, setting up timers, or interacting with the browser's DOM.

This guide breaks down the four main ways to use useEffect, making it easy to understand how to control when your code runs.

1. Run an Effect Only Once: `useEffect` with an Empty Array `[]`

This is the most common use case. When you provide an empty dependency array [], you're telling React to run the effect only once, when the component first loads (mounts). This is perfect for initial data fetching from an API or setting up a one-time subscription or event listener.

js
import { useEffect } from 'react';

useEffect(() => {
  // This code runs only once when the component mounts.
  console.log('Fetching initial data...');
  fetch('https://api.example.com/data');
}, []);

2. Run an Effect When a Value Changes: `useEffect` with Dependencies `[value]`

If you want an effect to run on the initial load and every time a specific value changes, you should include that value in the dependency array. This is ideal for re-fetching data when a user ID or search term changes or updating a value based on a change in props or state.

js
import React, { useState, useEffect } from 'react';

function UserProfile({ userId }) {
  const [userData, setUserData] = useState(null);

  useEffect(() => {
    // This runs on mount AND whenever the `userId` prop changes.
    console.log(`Fetching data for user: ${userId}`);
    fetch(`https://api.example.com/users/${userId}`);
  }, [userId]); // The dependency array tells React to watch for changes to `userId`.

  // ... rest of your component
}

3. Run an Effect on Every Render: `useEffect` with No Array

When you omit the dependency array entirely, the effect will run after every single render of the component. This is a less common pattern and should be used with caution, as it can easily lead to performance issues if you're not careful. It's useful for debugging or logging every state change.

js
import React, { useState, useEffect } from 'react';

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

  useEffect(() => {
    // This runs on every render, including the initial render and every time `count` is updated.
    console.log(`The component has rendered. Current count is ${count}`);
  }); // No dependency array means it runs on every render.

  // ... rest of your component
}

4. Clean Up Your Effects: `useEffect` with a Cleanup Function

Some side effects, like setting up a timer or an event listener, need to be cleaned up to prevent memory leaks. The useEffect hook lets you do this by returning a function from your effect. React will run this cleanup function when the component unmounts. If you have dependencies, it will also run the cleanup before the effect re-runs due to a dependency change.

js
import { useEffect } from 'react';

useEffect(() => {
  const timerId = setInterval(() => {
    console.log('Timer is running...');
  }, 1000);

  // This return function is the cleanup code.
  return () => {
    console.log('Cleaning up timer...');
    clearInterval(timerId);
  };
}, []); // The cleanup runs when the component unmounts.

Summary of `useEffect` Behaviors

The dependency array is the key to controlling your effects. Here's a quick reference:

js
useEffect(() => { ... }, []); // Runs once on mount.

useEffect(() => { ... }, [value]); // Runs on mount and whenever 'value' changes.

useEffect(() => { ... }); // Runs on every render.

useEffect(() => { 
  // Effect logic
  return () => { 
    // Cleanup logic 
  };
}, [value]); // Cleanup runs on unmount and before the effect re-runs.

Mastering the useEffect hook and its dependency array is essential for building efficient and reliable React applications. By understanding these simple patterns, you can confidently handle side effects and avoid common mistakes.