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.
The extra setup and cleanup cycle happens only in development. Users never see it in production.
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.
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.
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
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.
Frequently Asked Questions
Does useEffect run twice in production?
Is the double run a bug in my code?
Should I turn off Strict Mode to stop it?
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.
More in this topic
How to Build a Dropdown Menu in React
Build a React dropdown menu with the ARIA menu button pattern. Handle open and close, keyboard arrows, and clicks outside the menu.
How to Animate React Components with Motion
Animate React components with the Motion library. Set up motion, add enter, hover, and exit animations, and respect reduced motion.
Headless UI Components Explained: Logic Without Locked Styling
Understand headless UI components and how libraries like Radix give you unstyled, accessible behavior that you style yourself.