useEffect runs twice in development — React StrictMode double-invoking effects
In development, StrictMode intentionally mounts, unmounts, and remounts components once to surface Effects that aren't properly cleaned up — it's a diagnostic, not a bug, and it does not happen in production builds. If double-invocation causes visible problems (duplicate API calls, doubled state), the actual bug is a missing or incorrect cleanup function in useEffect.
What causes this error
StrictMode simulates unmounting and remounting a component immediately after its first mount, specifically to catch Effects whose setup isn't idempotent or whose cleanup is missing — problems that would otherwise only surface unpredictably in production under real remounts (like tab switching with certain routing setups). It only does this in development; production builds mount once as normal.
The Fix
// Wrong — no cleanup, so remounting leaves a duplicate subscription/interval
useEffect(() => {
const id = setInterval(tick, 1000);
}, []);
// Fix — always return a cleanup function that undoes the setup
useEffect(() => {
const id = setInterval(tick, 1000);
return () => clearInterval(id);
}, []);Resist the urge to remove <StrictMode> to make the symptom disappear — that hides the bug in development but doesn't fix the underlying issue, which can still bite you in production.
Common causes / variations
- A `fetch` or subscription set up in `useEffect` with no corresponding `abort()`/`unsubscribe()` in the cleanup
- An interval or timeout started without being cleared
- Treating an Effect's setup as something that should only ever run once, ever, rather than being safely re-runnable
Related errors
See also: Cannot update a component while rendering a different component, Activity not preserving state.
Comments
Loading comments...