Effects and Lifecycle

This section documents React 19.x for the web, using function components and Hooks throughout (class components appear only where React still requires them, such as error boundaries). React Native is out of scope and gets only a pointer. The React 19 additions follow the current official React documentation. This content was generated with the assistance of AI and should be verified against react.dev before being relied on in production, since React APIs continue to evolve between releases.

This section’s bibliography lists the reference material consulted while preparing these pages.

An Effect lets a component synchronize with a system outside React — the browser DOM, a network connection, a third-party widget, a timer. Effects run after render and commit, not during. If you are not synchronizing with an external system, you probably do not need an Effect.

useEffect

import { useEffect, useState } from 'react';

function ChatRoom({ roomId }) {
  const [status, setStatus] = useState('connecting');

  useEffect(() => {
    const conn = createConnection(roomId);   // setup
    conn.on('open', () => setStatus('online'));
    conn.connect();
    return () => conn.disconnect();           // cleanup
  }, [roomId]);                               // dependencies

  return <p>{status}</p>;
}
  • The setup function runs after the component mounts (and again after any dependency changes).

  • The cleanup function runs before the next setup and when the component unmounts.

  • The dependency array lists every reactive value (prop, state, or value derived from them) used inside. [] means "run once on mount". Omitting the array means "run after every render" — rarely what you want.

In development <StrictMode> runs setup → cleanup → setup once extra, to prove your cleanup is correct. References: useEffect, Synchronizing with Effects.

Fetching data in an Effect

Fetching in an Effect works but needs a race guard: a slow earlier request must not overwrite a faster later one.

useEffect(() => {
  let ignore = false;
  fetch(`/api/products/${id}`)
    .then((r) => r.json())
    .then((data) => { if (!ignore) setProduct(data); });
  return () => { ignore = true; };            // or: const c = new AbortController(); ... c.abort()
}, [id]);

For anything beyond a trivial case, a framework loader or a data library (TanStack Query, SWR) handles caching, deduplication, and races for you — see Data Fetching.

You might not need an Effect

Common Effects that should be deleted:

  • Deriving data — compute it during render instead of storing it in state and syncing with an Effect.

    // no Effect needed
    const fullName = firstName + ' ' + lastName;
    const visible = items.filter((i) => i.matches(query));
  • Responding to a user event — put that logic in the event handler, not an Effect that watches state.

  • Resetting state when a prop changes — give the component a different key so React remounts it.

  • Caching an expensive calculation — use useMemo, not state + Effect.

Reactive effects and useEffectEvent

An Effect is reactive: it re-runs when any dependency changes. Sometimes you want to read the latest value of something without making it a dependency — for example, log the current theme when a chat connects, but do not reconnect when only the theme changes. Extract that part into an Effect Event:

import { useEffect, useEffectEvent } from 'react';

function ChatRoom({ roomId, theme }) {
  const onConnected = useEffectEvent(() => {
    showToast('Connected!', theme);          // reads latest theme, not reactive
  });

  useEffect(() => {
    const conn = createConnection(roomId);
    conn.on('open', onConnected);
    conn.connect();
    return () => conn.disconnect();
  }, [roomId]);                              // theme is NOT a dependency
}

useLayoutEffect and useInsertionEffect

  • useEffect — runs after the browser paints. The default; use it unless you have a reason not to.

  • useLayoutEffect — runs before the browser paints, after DOM mutation. Use it only to measure layout (e.g. a tooltip’s size) and re-render synchronously so the user never sees the intermediate state. It blocks painting, so keep it cheap.

  • useInsertionEffect — runs before any layout effect; for CSS-in-JS libraries injecting <style> tags. Application code should not need it.

flowchart LR M["Mount
run setup"] --> R["Dep changes
run cleanup"] R --> S["run setup again"] S -->|"more dep changes"| R S --> U["Unmount
run cleanup (final)"]