gotchajavascriptreactModerate
React Strict Mode double-invokes render and effects — this is intentional
Viewed 0 times
React 18 (double-effect); React 16.3+ (double-render)
strict modedouble renderdouble invokedevelopmenteffect cleanupidempotent
browser
Problem
In development with StrictMode, useEffect cleanup and setup run twice on mount (and components render twice). This causes visible issues: network requests fire twice, subscriptions connect and disconnect immediately, and console.log appears twice.
Solution
Write effects that are resilient to double-invocation — this is what StrictMode is testing:
// BAD: effect not idempotent — doesn't handle double-invoke
useEffect(() => {
const connection = createConnection();
connection.connect();
// no cleanup — double-connect leaks
}, []);
// GOOD: effect with proper cleanup
useEffect(() => {
const connection = createConnection();
connection.connect();
return () => connection.disconnect(); // cleanup lets re-mount work correctly
}, []);
// For fetch — abort controller prevents double-request issues
useEffect(() => {
const controller = new AbortController();
fetch('/api/data', { signal: controller.signal })
.then(r => r.json())
.then(setData)
.catch(e => { if (e.name !== 'AbortError') setError(e); });
return () => controller.abort();
}, []);
// StrictMode wrapping (in index.jsx/main.jsx)
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// BAD: effect not idempotent — doesn't handle double-invoke
useEffect(() => {
const connection = createConnection();
connection.connect();
// no cleanup — double-connect leaks
}, []);
// GOOD: effect with proper cleanup
useEffect(() => {
const connection = createConnection();
connection.connect();
return () => connection.disconnect(); // cleanup lets re-mount work correctly
}, []);
// For fetch — abort controller prevents double-request issues
useEffect(() => {
const controller = new AbortController();
fetch('/api/data', { signal: controller.signal })
.then(r => r.json())
.then(setData)
.catch(e => { if (e.name !== 'AbortError') setError(e); });
return () => controller.abort();
}, []);
// StrictMode wrapping (in index.jsx/main.jsx)
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
Why
React 18 StrictMode simulates unmounting and remounting components in development to surface bugs: effects that don't clean up, state that relies on effect order, and non-idempotent setup code. In production, effects run once. The double-invoke only happens in development.
Gotchas
- Double-invoke only occurs in development — production is unaffected
- If your effect breaks under double-invoke, the bug is in your code, not StrictMode
- React 18 added the double-effect behavior — React 17 StrictMode only double-rendered, not effects
- Removing StrictMode to silence the issue hides real bugs that will appear in production
Code Snippets
StrictMode effect lifecycle
// React 18 StrictMode effect lifecycle in development:
// mount → setup → cleanup → setup (simulates remount)
// Always write effects that survive this cycleContext
When debugging behavior that occurs twice in development but not in production due to React StrictMode
Revisions (0)
No revisions yet.