`[]` 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.
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:
- Run the effect
- Run the cleanup
- 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) →