Codepath

React useEffect Hook

Overview

The useEffect hook is a fundamental React hook that enables functional components to perform side effects. It replaces lifecycle methods like componentDidMount, componentDidUpdate, and componentWillUnmount from class components.

This guide covers:

  • What useEffect is and what side effects are
  • When and how to use useEffect
  • Syntax and the dependency array
  • Common mistakes and how to avoid them
  • Additional resources for deeper learning

What is the useEffect Hook?

The useEffect hook lets you synchronize a component with external systems or perform actions that aren't directly tied to rendering, such as fetching data or updating the DOM.

What is a Side Effect?

A side effect is any operation that affects something outside the scope of the component's render function. Examples include:

  • Fetching data from an API (arguably the most commonly-used side effect)
  • Subscribing to events (e.g., WebSocket, browser events)
  • Updating the document title
  • Manipulating the DOM directly
  • Setting up timers (e.g., setTimeout, setInterval)
graph TD
    A[Component Renders] --> B{Render Logic}
    B --> C[Output UI]
    B --> D[useEffect]
    D --> E[Side Effects]
    E --> F[External Systems]
    F --> |Updates| A

📖 Read more: Synchronizing with Effects


When Should I Use useEffect?

Use useEffect when your component needs to handle side effects at specific moments in its lifecycle.

1. After Component Mount

Run code once after the component is added to the DOM (similar to componentDidMount).

useEffect(() => {
  // Fetch data when component mounts
  fetch('https://api.example.com/data')
    .then(res => res.json())
    .then(data => console.log(data));
}, []); // Empty array = run once on mount

2. When Dependencies Change

Run code whenever specific values (dependencies) change (similar to componentDidUpdate).

useEffect(() => {
  document.title = `User: ${userId}`;
}, [userId]); // Runs when userId changes

3. Cleanup

Run cleanup code before the component unmounts or before the effect runs again (similar to componentWillUnmount).

useEffect(() => {
  const timer = setInterval(() => console.log('Tick'), 1000);
  
  // Cleanup: runs on unmount or before next effect
  return () => clearInterval(timer);
}, []); // Empty array = cleanup on unmount
graph TD
    A[Component Mounts] --> B[Run useEffect]
    B --> C[Dependency Changes?]
    C -->|Yes| D[Cleanup Old Effect]
    D --> B
    C -->|No| E[Do Nothing]
    A --> F[Component Unmounts]
    F --> G[Run Cleanup]

🎬 Watch: React useEffect Hook Explained


useEffect Syntax

The useEffect hook takes two arguments:

  1. Effect Function: The code to run for the side effect.
  2. Dependency Array (optional): Controls when the effect runs.
useEffect(() => {
  // Effect code here
  console.log('Effect ran');

  // Optional cleanup function
  return () => {
    console.log('Cleanup ran');
  };
}, [dependency1, dependency2]); // Dependency array
  • No dependency array: Effect runs after every render.
  • Empty array ([]): Effect runs once on mount, cleanup on unmount.
  • Array with values: Effect runs on mount and when listed dependencies change.

📖 Read more: useEffect Reference


What is a Dependency Array?

The dependency array is an optional second argument to useEffect that lists variables the effect depends on. It controls when the effect (and its cleanup) runs.

How Does it Relate to useEffect?

  • React compares the current values of dependencies with their previous values.
  • If any value changes (using shallow comparison), the effect runs after the render.
  • If the array is empty, the effect only runs once on mount.
graph LR
    A[Render] --> B{Dependency Array}
    B -->|Empty| C[Run Once on Mount]
    B -->|No Array| D[Run Every Render]
    B -->|Has Values| E{Values Changed?}
    E -->|Yes| F[Run Effect]
    E -->|No| G[Skip Effect]

Example

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

useEffect(() => {
  console.log(`Count is now ${count}`);
}, [count]); // Only runs when count changes

📖 Read more: How the Dependency Array Works


Common Mistakes with useEffect

1. Infinite Loops

Forgetting to include dependencies or updating a dependency inside the effect can cause infinite re-renders.

// ❌ Bad: Infinite loop
const [data, setData] = useState(null);
useEffect(() => {
  setData(fetchData()); // Updates state every render
}, []); // Missing data dependency

// ✅ Good: Include all dependencies
useEffect(() => {
  setData(fetchData());
}, [fetchData]); // Include function if it changes

2. Missing Cleanup

Not cleaning up subscriptions or intervals can lead to memory leaks.

// ❌ Bad: No cleanup
useEffect(() => {
  const id = setInterval(() => console.log('Tick'), 1000);
}, []);

// ✅ Good: Cleanup
useEffect(() => {
  const id = setInterval(() => console.log('Tick'), 1000);
  return () => clearInterval(id);
}, []);

3. Overusing useEffect

Avoid using useEffect for logic that could be handled in event handlers or render logic.

// ❌ Overcomplicated
const [value, setValue] = useState(0);
useEffect(() => {
  setValue(Math.random());
}, []);

// ✅ Simpler: Use initial state
const [value, setValue] = useState(Math.random());

📖 Read more: You Might Not Need an Effect


Additional References

Fork me on GitHub