Learn / Frameworks / React / Effects with useEffect

React · Lesson 5 of 7

Effects with useEffect

Synchronize with the outside world: fetching, timers and subscriptions.

  • Intermediate
  • 16 min read
  • 3 objectives

Before this lessonLesson 4: Events, Lists and Forms

What you will learn

  • Write effects with correct dependencies
  • Clean up effects
  • Know when not to use an effect

Rendering should be a pure calculation: same props and state in, same JSX out. But apps also need to talk to things outside React: servers, timers, the browser title, event listeners. Effects are the escape hatch. useEffect runs your code after React has updated the screen.

Anatomy

import { useState, useEffect } from "react";

function Title({ name }) {
  useEffect(() => {
    document.title = `Hello ${name}`;   // runs after render
  }, [name]);                            // ...only when name changes
  return <h1>{name}</h1>;
}

The second argument is the dependency array:

  • [a, b]: run after the first render and whenever a or b changed.
  • []: run once after the first render.
  • omitted: run after every render (rarely what you want).

List every reactive value the effect reads (props, state, values derived from them). The lint rule react-hooks/exhaustive-deps checks this for you; treat its warnings seriously, because stale values are a top source of bugs.

Cleanup

An effect may return a function that React calls before the effect re-runs and when the component unmounts. Use it to stop timers, remove listeners and cancel subscriptions.

function Clock() {
  const [now, setNow] = useState(new Date());
  useEffect(() => {
    const id = setInterval(() => setNow(new Date()), 1000);
    return () => clearInterval(id);     // cleanup
  }, []);
  return <p>{now.toLocaleTimeString()}</p>;
}

In development, StrictMode runs effects twice (mount, unmount, mount) on purpose. If your effect breaks when run twice, it is missing a cleanup.

Fetching data

function User({ id }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    let ignore = false;                 // avoid setting state from a stale request
    fetch(`/api/users/${id}`)
      .then((r) => r.json())
      .then((data) => { if (!ignore) setUser(data); });
    return () => { ignore = true; };
  }, [id]);

  return user ? <p>{user.name}</p> : <p>Loading...</p>;
}

You might not need an effect

  • Computing a value from props or state: do it during render, no effect needed.
  • Responding to a click or submit: put the logic in the event handler.
  • Resetting state when a prop changes: give the component a different key.
// Write your solution here
Up next · Lesson 6Custom Hooks, Refs and ContextReuse logic with custom hooks and share data with context.