Mount-Only Effects

`[]` Means "Once", But StrictMode Double-Fires in Dev

Mount-Only Effects

An effect with `[]` runs once after mount. In dev with StrictMode it runs twice — that's a feature, not a bug. Cleanup makes it work.

3 min read Level 2/5 #react#useEffect#mount
What you'll learn
  • Understand when to use `[]`
  • Survive StrictMode's double-fire
  • Recognize what doesn't belong in an "on mount" effect

An effect with [] as the dependency array runs once after the component first mounts. It’s perfect for things that need to happen exactly one time per instance — log a page view, attach a global listener, start a websocket.

useEffect(() => {
  trackPageView();
}, []);

StrictMode Fires It Twice In Dev

In React 18+ with <StrictMode>, every mount-only effect runs twice during development:

  1. Run the effect
  2. Run the cleanup
  3. Run the effect again

This is intentional. The double-fire surfaces effects that won’t survive a re-mount (which can happen for real in some routing scenarios). If your effect breaks on the second run, the bug is yours — usually a missing cleanup.

In production builds, the effect runs exactly once.

Make It Idempotent

The fix for double-fire is to make the effect safe to run twice. Most of the time that means adding a cleanup:

useEffect(() => {
  const id = setInterval(tick, 1000);
  return () => clearInterval(id);   // ← double-mount-safe
}, []);

Things That Should NOT Be []-Only

A mount-only effect should not reference anything that can change:

// ✗ The fetch uses `id`, but `id` isn't in deps → stale fetches
useEffect(() => {
  fetch(`/api/users/${id}`);
}, []);

// ✓ Re-runs when `id` changes
useEffect(() => {
  fetch(`/api/users/${id}`);
}, [id]);

If you have to include something to make the linter happy and you genuinely don’t want it to re-trigger, restructure the code — pull the value behind a ref, or move the effect into a child whose key captures the value.

Real “Once Per Lifetime” Things

Truly mount-only effects are rare. Some examples:

  • Telemetry “page viewed”
  • Initialize a third-party SDK
  • A one-shot timer that intentionally doesn’t reset on prop change

For “fetch once when something changes”, use a dep array.

What About Unmount-Only?

There’s no useUnmount hook. To run code only on unmount, use the cleanup of a [] effect:

useEffect(() => {
  return () => {
    // runs on unmount
    saveDraftIfNeeded();
  };
}, []);

(Same StrictMode double-fire applies — be sure the cleanup is also safe to run twice in dev.)

Up Next

Refs are how you point at a DOM node. Time to meet them.

DOM Refs (Preview) →