React useEffect Hook

The React useEffect hook lets you run side effects: code that reaches outside React, such as fetching data, setting timers, subscribing to events or changing the document title. Effects run after React has updated the screen.

Basic useEffect Syntax

The first argument is the effect function. The second is the dependency array: React re-runs the effect only when one of these values has changed since the last render.

import { useState, useEffect } from 'react';

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

  useEffect(() => {
    document.title = `Clicked ${count} times`;
  }, [count]);

  return <button onClick={() => setCount(count + 1)}>Click</button>;
}
(browser tab title)
Clicked 0 times -> Clicked 1 times -> ...

The Dependency Array

  • useEffect(fn) — no array: runs after every render.
  • useEffect(fn, []) — empty array: runs once after the first render.
  • useEffect(fn, [a, b]) — runs after the first render and whenever a or b changes.
  • Include every prop and state value the effect uses; the eslint-plugin-react-hooks rule warns you if you forget one.

Cleaning Up Effects

If your effect starts something that keeps running, return a cleanup function. React calls it before the effect runs again and when the component is removed.

function Clock() {
  const [time, setTime] = useState(new Date());

  useEffect(() => {
    const id = setInterval(() => setTime(new Date()), 1000);
    return () => clearInterval(id);
  }, []);

  return <p>{time.toLocaleTimeString()}</p>;
}
10:42:07 AM
10:42:08 AM
...

Listening to Window Events

Without the cleanup, every mount would add another listener and leak memory.

function WindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);

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

  return <p>Window width: {width}px</p>;
}
Window width: 1280px

Avoiding Infinite Loops

If an effect sets state that is also in its own dependency array, or runs on every render and sets state, it triggers a render that triggers the effect again, forever. Fix it by choosing the right dependencies, or by asking whether you need an effect at all: values you can calculate from props or state should be computed during render, not stored with an effect.

Strict Mode Runs Effects Twice

In development, React 18 Strict Mode mounts, unmounts and re-mounts components to help you find missing cleanups. If you see an effect run twice locally, that is expected; it only happens in development.

Frequently asked questions

How do I make useEffect run only once?

Pass an empty dependency array: useEffect(() => { ... }, []). It then runs after the first render only (twice in development Strict Mode, by design).

Why is my useEffect running in an infinite loop?

The effect updates state that causes it to run again, often because an object or array dependency is recreated every render or the dependency array is missing. Fix the dependencies or compute the value without an effect.

What is the useEffect cleanup function for?

It undoes what the effect set up, such as clearing intervals, removing event listeners or cancelling requests. React runs it before re-running the effect and on unmount.

Can useEffect be async?

The effect function itself cannot be async because it must return nothing or a cleanup function. Define an async function inside the effect and call it.