Why Is My useEffect Running Twice? (And What to Do About It)
React Strict Mode intentionally double-invokes effects in development to surface missing cleanup. Why it happens, why it's a feature, and how to write effects that are correct either way.
The short answer: your effect runs twice because React Strict Mode intentionally mounts, unmounts, and re-mounts your component in development to check that your effect cleans up after itself. It's a feature, not a bug, and it only happens in dev — production runs the effect once. The fix is almost never "stop it from running twice"; it's "write an effect that's correct no matter how many times it runs," which means a proper cleanup function.
Everyone hits this. You add a useEffect with a console.log or a fetch, and it fires twice on page load. You didn't change the dependencies, nothing's wrong with your deps array, and yet — twice. Here's exactly what's happening and why trying to suppress it is the wrong instinct.
It's Strict Mode, on purpose
Since React 18, <React.StrictMode> (which create-next-app and most templates wrap your app in by default) does something deliberate in development: it mounts your component, runs effects, immediately runs their cleanup, and then runs the effects again. So in dev you see:
React is simulating a mount → unmount → remount cycle to flush out effects that don't clean up correctly — because in a real app, components do unmount and remount (navigation, conditional rendering, Fast Refresh), and an effect that leaks on unmount is a real bug. Strict Mode just makes that bug visible immediately instead of in production three weeks later.
This does not happen in production. Build your app and the effect runs once. If you only care that the double-run stops, shipping already does that — but that misses the point Strict Mode is making.
Why you shouldn't "fix" it by disabling Strict Mode
The reflex is to rip out <StrictMode> so the noise stops. Don't. The double-invoke isn't the problem; it's the smoke detector. If running your effect twice breaks something — a duplicate network request that creates two records, a listener added twice, a subscription that never closes — that's a real defect that will also bite you on any genuine remount. Deleting Strict Mode hides it until a user finds it.
The actual fix: make the effect idempotent with cleanup
An effect that's correct under Strict Mode is correct everywhere. The rule: whatever an effect sets up, its cleanup must tear down.
For data fetching, guard against the first (torn-down) run resolving after the component already cleaned up — otherwise you set state from a stale request:
With AbortController, the first Strict-Mode run is aborted by its own cleanup, the second run wins, and you never set state from the discarded request. This is the pattern that's robust in dev, in production, and on every real remount.
"But I only want it to run once — ever"
Sometimes you have a genuinely one-time side effect (fire an analytics event, run a one-off init). The right tool there isn't suppressing Strict Mode — it's recognising that the effect running twice in dev is still fine if the effect is idempotent. For truly non-idempotent one-shots, gate on a ref:
Reach for this only when the side effect genuinely must happen exactly once and can't be made idempotent — not as a blanket way to silence Strict Mode. Most effects should just have correct cleanup instead.
The mental shift
Stop asking "how do I stop my effect from running twice" and start asking "is my effect correct if it runs, cleans up, and runs again?" If the answer is yes, Strict Mode's double-invoke is invisible to your app's behaviour and you can ignore it. If the answer is no, you've just found a bug that would have shipped. That's the whole purpose of the feature — and it pairs with the broader set of useEffect mistakes worth unlearning, most of which also trace back to missing or wrong cleanup.
Frequently asked questions
Why does my useEffect run twice?
React Strict Mode intentionally mounts, unmounts, and re-mounts your component in development to verify your effect cleans up correctly. It's deliberate, and it only happens in development — production runs the effect once.
Should I disable Strict Mode to stop the double run?
No. The double-invoke is a smoke detector, not the fire. If running twice breaks something (a duplicate request, a leaked subscription) that's a real bug that will also appear on genuine remounts. Fix the effect's cleanup instead of removing Strict Mode.
How do I make an effect run only once?
Prefer making the effect idempotent with proper cleanup, which is correct no matter how many times it runs. For a genuinely non-idempotent one-shot (like a one-time analytics init) guard it with a useRef flag, used sparingly.