React useEffect Runs Twice: Why It Happens and How to Fix Your Logic

useEffect runs twice in development because Strict Mode mounts, unmounts, and remounts each component once. Learn why and how to make your Effect survive it.

6 min read

When useEffect runs twice in development, it is React testing your Effect on purpose. Strict Mode mounts, unmounts, and remounts each component once, so the Effect runs setup, cleanup, and setup again. This never happens in a production build.

The double run is not a defect in your component. It is a stress test designed to expose Effects that leak work after a remount. Fixing the Effect is the correct response, not hiding the second run.

Why the double run happens

Strict Mode enables extra checks during development. It re-renders components an extra time and re-runs Effects an extra time to reveal bugs that only appear after a remount. The double render also runs impure functions twice, so accidental side effects show up early.

Development only

The extra setup and cleanup cycle happens only in development. Users never see it in production.

Strict Mode runs setup twice in development

The sequence shows the development mount. The extra cycle is the same thing that would happen if a user visited the page, navigated away, and came back. If that round trip leaks a timer or a connection, the Effect is missing cleanup.

Think of it as opening and closing a door one extra time to check the lock. React starts and stops the Effect once more in development to confirm the cleanup mirrors the setup.

A timer that ticks twice

This counter is supposed to increment once per second. In development it increments twice. The bug is visible immediately, which is the point of the extra cycle.

App.jsxApp.jsx
import { useEffect, useState } from "react";
 
function Timer() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    setInterval(() => setCount((c) => c + 1), 1000);
  }, []);
  return <p>{count}</p>;
}

The visible result is a counter that climbs by two every second in development. The setup creates an interval but never clears it, so the first mount leaves a second interval running alongside the one from the remount. In production the leak is quieter: the counter ticks once, but the interval keeps running after the component unmounts.

The fix is a cleanup function

The Effect should undo what the setup started. Save the interval id and clear it in the cleanup. The cleanup then removes the interval whether React is about to re-run the Effect or unmount the component.

App.jsxApp.jsx
import { useEffect, useState } from "react";
 
function Timer() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    const id = setInterval(() => setCount((c) => c + 1), 1000);
    return () => clearInterval(id);
  }, []);
  return <p>{count}</p>;
}

Now the counter increments once per second in development and in production. The first interval is cleared before the second is created, so only one timer ever runs at a time. The cleanup also clears the last interval on unmount, so no hidden timer keeps counting.

When running twice is harmless

Some Effects do not need a special fix because running twice has the same visible result. The rule is that setup, cleanup, setup should look the same to a user as setup alone.

  • Calling a setter twice with the same value, like setting a map zoom level.
  • Sending analytics that is already disabled in development.
  • Reading the latest value without changing any state.
  • A fetch whose cleanup ignores the stale response.

The question is never how to force a single run. It is whether the Effect survives a remount. Do not reach for a ref to make the Effect run once; that hides the leak instead of fixing it.

Cleaning up timers, listeners, and subscriptions covers the general pattern, and the StrictMode article lists every development check.

What to learn next

The extra cycle happens on mount, while normal re-runs happen when dependencies change. See how the dependency array works for that side of the story.

A well-written cleanup makes both cases invisible to the user. Once the cleanup is correct, the double run stops being noticeable even in development.

Rune AI

Rune AI

Key Insights

  • The double run is development-only and comes from Strict Mode remounting components.
  • Strict Mode runs setup, cleanup, then setup again on the first mount.
  • The real bug is usually a missing cleanup function, not the double run itself.
  • Fix the Effect to be resilient to remounting instead of disabling Strict Mode.
RunePowered by Rune AI

Frequently Asked Questions

Does useEffect run twice in production?

No. The extra setup and cleanup cycle happens only in development when Strict Mode is enabled. The production build runs the Effect once.

Is the double run a bug in my code?

Usually not. React remounts components on purpose to reveal missing cleanup logic. If the double run breaks something, the cleanup function is incomplete.

Should I turn off Strict Mode to stop it?

It is better to fix the Effect so it works after remounting. Strict Mode exposes the same bug a user would hit when navigating away and back.

Conclusion

A double useEffect run in development is Strict Mode stress-testing your Effect, not a defect. Add a cleanup function that mirrors the setup, and the Effect behaves the same whether it runs once or twice.