Overview

useEffect lets a React component synchronize with systems outside React: data fetching, subscriptions, timers, and direct DOM manipulation. It is also the source of many bugs — infinite loops, stale closures, and memory leaks. This tutorial explains how to use it correctly.

Basic Syntax

import { useEffect } from 'react';

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

The first argument is the effect function. The second argument is the dependency array that controls when the effect runs.

Three Behaviors Based on the Dependency Array

Dependency arrayWhen the effect runs
Omitted entirelyAfter every render
[] (empty)Only after the first render
[value]After first render and whenever value changes

Example: Fetching Data

import { useState, useEffect } from 'react';

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

  useEffect(() => {
    let cancelled = false;

    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(data => {
        if (!cancelled) setUser(data);
      });

    return () => {
      cancelled = true;
    };
  }, [userId]);

  if (!user) return <p>Loading...</p>;
  return <h1>{user.name}</h1>;
}

The cancelled flag prevents setting state on an unmounted component, which avoids React warnings and memory leaks.

Cleanup Functions

Return a function from the effect to clean up subscriptions, timers, or event listeners:

useEffect(() => {
  const interval = setInterval(() => {
    console.log('tick');
  }, 1000);

  return () => clearInterval(interval);
}, []);

React runs the cleanup before the next effect execution and when the component unmounts.

Common Bug: Infinite Loop

// BUG: object in dependency array
useEffect(() => {
  setCount(count + 1);
}, [count]); // runs forever

// FIX: use functional update and empty dependencies
useEffect(() => {
  setCount(c => c + 1);
}, []);

The infinite loop happens because setCount changes count, which triggers the effect again. Use functional updates when the new state depends on the previous state.

When Not to Use useEffect

  • Deriving state from props — compute it during render instead.
  • Transforming data for rendering — use useMemo.
  • Handling user events — put logic in the event handler, not in an effect.
  • Fetching data in React 18+ — consider React Query, SWR, or framework-level loaders.

Dependency Array Checklist

QuestionAction
Does the effect use a prop or state?Include it in the dependency array
Does the effect use a function defined in the component?Wrap it in useCallback or move it inside the effect
Does the effect use an object or array?Use a primitive value or memoize the object