- #react
- #useeffect
- #strict-mode
Why useEffect runs twice in dev (and why it's a good thing)
When you migrate to React 18+ (and React 19 now), you'll notice your useEffect runs twice in development. This isn't a bug — it's React Strict Mode intentionally exercising your effect's cleanup logic to surface bugs early.
What's actually happening
In Strict Mode, React mounts → unmounts → remounts every component once during development. The console reveals the pattern:
useEffect(() => {
console.log("mounted");
return () => console.log("cleanup");
}, []);
// Dev console: "mounted" → "cleanup" → "mounted"Production behavior is unchanged — Strict Mode is dev-only.
Why React does this
Future React versions may preserve component state across unmount/remount cycles (think: router transitions, suspense boundaries, fiber-aware features). Strict Mode tests that your cleanup logic is solid TODAY so future refactors don't silently break.
Fix: write proper cleanup
If your effect has side effects — subscriptions, timers, fetch requests — always return a cleanup function. If your effect "breaks" under Strict Mode, it's likely leaking resources in production too.
Need to inspect stale state? Drop your component's props into the JSON Formatter to spot the shape mismatch.