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
keyso 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.
run setup"] --> R["Dep changes
run cleanup"] R --> S["run setup again"] S -->|"more dep changes"| R S --> U["Unmount
run cleanup (final)"]
References: useLayoutEffect,
useInsertionEffect.