What is React Strict Mode and how does it affect development?
Strict Mode is a dev-only React wrapper that surfaces unsafe patterns: double-invokes components/effects/state initializers, warns on deprecated APIs (legacy refs, findDOMNode), forbids side-effects in render. The double-mount in React 18 specifically reveals missing useEffect cleanups. Production is unchanged. Always wrap the app in <StrictMode> during development.
Strict Mode is a development-only React wrapper that helps surface bugs that would otherwise lurk until they bite in production.
Setup
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
</StrictMode>,
);What it does
- Double-invokes components, effects, state initializers, and reducers in dev.
- Warns on legacy APIs:
findDOMNode, string refs, legacy context, deprecated lifecycles (UNSAFE_componentWillMount, etc.). - Detects side-effects in render (warnings in dev when something would break concurrent rendering).
- Forces you to handle effect cleanup correctly — the double-mount surfaces leaks.
The double-mount in React 18
useEffect(() => {
console.log('mount');
return () => console.log('cleanup');
}, []);In dev with StrictMode, this logs: mount → cleanup → mount. React simulates an unmount + remount to make sure your effect handles being re-run safely.
If you see a bug only with StrictMode (subscribe twice, fetch twice), the fix is not to disable StrictMode — it's to add proper cleanup so the second run is idempotent.
Why this matters
React 18's concurrent rendering can interrupt and re-run renders. Effects that aren't properly cleaned up will leak in production under realistic load. StrictMode surfaces the bug in dev where it's cheap to fix.
Common surfaces
| Issue | Symptom | Fix |
|---|---|---|
| Effect with no cleanup | API hit twice | AbortController in cleanup |
| Subscription leaks | Listener fires twice | Unsubscribe in cleanup |
| Non-idempotent state init | State mutates | Use updater fn in setState |
| Side-effects in render | Inconsistent UI | Move to useEffect |
| Legacy lifecycle methods | Warning | Migrate to hooks |
Production
StrictMode is completely no-op in production. No double-mount, no extra cost, no warnings. So you can leave it on permanently.
Common confusion
- "Strict Mode breaks my code" — usually means your effect doesn't clean up properly. The "breakage" is exposing an existing bug.
- "My counter goes from 0 to 2 in dev" — likely setting state without an updater fn or a setState during render.
Migrating an existing app
Wrap the app, run dev, fix everything it warns about. Often catches 5–10 real bugs in a medium app.
Interview framing
"Dev-only wrapper that surfaces unsafe patterns. Double-invokes components, effects, state initializers; warns on legacy APIs; detects side-effects in render. The double-mount specifically reveals missing useEffect cleanups — if your effect breaks under StrictMode, it would leak in production under concurrent rendering or remounts. The right fix is always to add cleanup, never to disable StrictMode. Production behavior is unchanged."
Follow-up questions
- •Why does StrictMode double-mount?
- •When have you fixed a real bug exposed by StrictMode?
- •What does it warn about that hooks don't catch?
Common mistakes
- •Disabling StrictMode to silence warnings.
- •Setting state in render.
- •Effects without cleanup.
Performance considerations
- •No prod cost. Dev double-invokes slow down dev cycles slightly; worth the bug-catching.
Edge cases
- •Storybook + StrictMode + fast-refresh interactions.
- •Legacy lifecycle methods in nested vendor components.
- •Initial-mount setup that's expensive (intentional one-time work).
Real-world examples
- •React 18 upgrade guides, double-fetch debugging stories.