Skip to content
← All tips
1 min read
  • #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.