Strict Mode
Reviewed & published by Brayan K
Strict Mode is a development-only wrapper, written <React.StrictMode>, that intentionally double-invokes renders and effects to surface impure code and missing cleanup. It runs nothing extra in production; its goal is to help you catch side-effect bugs early by making them obvious during development.
Part of the free React course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
What You'll Learn
- What <React.StrictMode> wraps and does
- Why renders and effects double-invoke in dev
- How double-invoke surfaces impure code
- Why it is development-only (no prod cost)
- Write effects with correct cleanup
- Keep render pure: same input, same output
1️⃣ What Strict Mode Is
You opt in by wrapping part of your tree in <React.StrictMode> (often the whole app at the root). In development it adds extra checks: it double-invokes component render functions, double-runs effects with their cleanup, and warns about deprecated APIs. None of this ships to production — the production build renders once and runs each effect once.
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
createRoot(document.getElementById("root")).render(
<StrictMode>
<App />
</StrictMode>
);
// Dev: extra checks. Prod: normal single render/effect.// PURE render: same inputs -> same output, NO side effects.
// Running it twice must produce the same result with no surprises.
function renderPrice(qty, unit) {
return qty * unit; // pure: depends only on inputs
}
console.log(renderPrice(2, 5)); // 10
console.log(renderPrice(2, 5)); // 10 <- StrictMode double-call: identical
console.log(renderPrice(3, 5)); // 152️⃣ Double-Invoke Surfaces Impurity
A component's render must be pure: given the same props and state it returns the same JSX and causes no side effects. By calling render twice, Strict Mode exposes impure code — if a render mutates a module variable, writes to the DOM, or sends a request, the second call drifts or duplicates, making the bug visible immediately instead of in production.
// ❌ Impure: mutates external state during render
let id = 0;
function Bad() {
id += 1; // side effect — StrictMode reveals double increment
return <span>{id}</span>;
}
// ✅ Pure: derive from props/state, no external mutation
function Good({ index }) {
return <span>{index}</span>;
}// IMPURE render mutates outside state — double-invoke EXPOSES the bug.
let counter = 0;
function impureRender() {
counter += 1; // ❌ side effect during render
return counter;
}
console.log(impureRender()); // 1
console.log(impureRender()); // 2 <- StrictMode's 2nd call reveals the drift!
// A pure version would NOT touch 'counter' during render.Your turn — pair subscribe with cleanup:
// 🎯 YOUR TURN — fill in each ___ then press "Try it Yourself".
// Make this effect SAFE under StrictMode by pairing each subscribe with a
// cleanup. Return a cleanup function that records "cleanup".
const events = [];
function safeEffect() {
events.push("subscribe");
return () => events.___("cleanup"); // 👉 push
}
const stop = safeEffect(); // mount
stop(); // StrictMode unmount
safeEffect(); // remount
console.log(events.___(" -> ")); // 👉 join
// ✅ Expected output:
// subscribe -> cleanup -> subscribe3️⃣ Writing Effects That Survive It
In development Strict Mode runs an effect, runs its cleanup, then runs the effect again — simulating an immediate unmount/remount. An effect that cleans up everything it sets up (clear timers, abort fetches, unsubscribe) passes this test invisibly. If you see a double subscription, a leaked interval, or a doubled request, that is the missing cleanup talking.
function Chat({ roomId }) {
useEffect(() => {
const conn = createConnection(roomId);
conn.connect();
return () => conn.disconnect(); // ✅ cleanup pairs with setup
}, [roomId]);
return <div>Room {roomId}</div>;
}
// StrictMode dev cycle: connect -> disconnect -> connect. Balanced = safe.// A SAFE effect cleans up fully, so running twice leaves no mess.
// Simulate: subscribe -> cleanup -> subscribe (StrictMode's dev cycle).
const log = [];
function effect() { log.push("subscribe"); return () => log.push("cleanup"); }
const cleanup1 = effect(); // mount
cleanup1(); // StrictMode unmount
const cleanup2 = effect(); // remount
// cleanup2 would run on real unmount
console.log(log.join(" -> "));
// subscribe -> cleanup -> subscribe
console.log(log.filter(x => x === "subscribe").length === log.filter(x => x === "cleanup").length + 1);
// true -> balanced (one live subscription remains)🧩 Reorder Challenge
These steps describe what Strict Mode does to an effect in development. Put them in the right order:
A. Run the cleanup function
B. Mount the component
C. Run the effect a SECOND time
D. Run the effect the first time
E. Verify setup and cleanup are balancedCommon Errors (and the fix)
- "My fetch fires twice." — Missing cleanup. Abort the request in cleanup (AbortController) so the double-invoke is harmless.
- Disabling Strict Mode to "fix" double effects. — That hides a real cleanup bug. Add the cleanup instead.
- Side effects during render. — Mutating state or the DOM in render is impure. Move it into useEffect.
- Expecting double behavior in production. — It only happens in dev. Do not rely on it.
📋 Quick Reference
| Aspect | Behavior |
|---|---|
| Wrapper | <React.StrictMode> |
| Render in dev | invoked twice (must be pure) |
| Effect in dev | run, cleanup, run again |
| Production | single render & effect |
| The fix | pure render + full cleanup |
🧠 Quick Recall
1. Is the double-invoke a bug in React?
No. It is intentional in development to surface impure renders and missing cleanup. Production runs once.
2. Your effect runs twice and leaks. What is missing?
A cleanup function that undoes the setup (clear the timer, abort the fetch, unsubscribe).
3. What must a render never do?
Cause side effects — it must be pure: same inputs produce the same output with no external mutation.
Mini-Challenge: A Leak-Free Timer Effect
Simulate Strict Mode's start/cleanup/start cycle for an interval effect and prove only one timer remains active. Run it and check your output.
// 🎯 MINI-CHALLENGE: prove a timer effect is StrictMode-safe
//
// An effect starts an interval and must clear it on cleanup.
// Simulate the dev cycle: start -> clear -> start, and verify only ONE
// timer is left running.
// 1. start() pushes a timer id and returns a cleanup that removes it.
// 2. Run start, then its cleanup, then start again.
// 3. console.log how many timers are still active (should be 1).
//
// ✅ Expected output:
// 1
const active = [];
let nextId = 1;
// your code here🎉 Lesson Complete
- ✅ <React.StrictMode> adds dev-only checks
- ✅ It double-invokes renders and effects to surface bugs
- ✅ Keep render pure — no side effects
- ✅ Pair every effect setup with a cleanup
Practice quiz
What does <React.StrictMode> do in development?
- It enables TypeScript checking
- It minifies your bundle
- It intentionally double-invokes renders and effects to surface impure code and missing cleanup
- It blocks deprecated APIs from running
Answer: It intentionally double-invokes renders and effects to surface impure code and missing cleanup. Strict Mode is a dev-only wrapper that double-invokes renders and re-runs effects (with cleanup) so side-effect bugs become visible early.
Does Strict Mode affect your production build?
- No — all its extra invocations and warnings are development-only
- Yes, it doubles every render in production too
- Only if you enable a flag
- It removes your effects in production
Answer: No — all its extra invocations and warnings are development-only. Production renders once and runs each effect once. Strict Mode's extra checks happen only in development.
Why does Strict Mode double-invoke your render function?
- To make the app faster
- To pre-cache the output
- To test network calls
- To surface impure renders — if render mutates external state, the second call drifts and exposes the bug
Answer: To surface impure renders — if render mutates external state, the second call drifts and exposes the bug. A render must be pure. Calling it twice reveals impurity: a mutated module variable or side effect during render shows up immediately.
What must a render function never do?
- Return JSX
- Cause side effects — it must be pure: same inputs, same output
- Read its props
- Use derived values
Answer: Cause side effects — it must be pure: same inputs, same output. Render must be pure. Side effects (mutating state, writing the DOM, sending requests) belong in useEffect, not in the render body.
In dev, what cycle does Strict Mode run for an effect?
- Run the effect, run its cleanup, then run the effect again
- Run the effect twice with no cleanup
- Run only the cleanup
- Skip the effect entirely
Answer: Run the effect, run its cleanup, then run the effect again. Strict Mode simulates an immediate unmount/remount: setup, cleanup, setup. A correctly-cleaned effect passes this invisibly.
Your fetch fires twice in development. What is the proper fix?
- Disable Strict Mode
- Wrap the fetch in setTimeout
- Add a correct cleanup (e.g. abort the request with AbortController) so the double-invoke is harmless
- Move the fetch into render
Answer: Add a correct cleanup (e.g. abort the request with AbortController) so the double-invoke is harmless. A double fetch signals missing cleanup. Abort or cancel in the cleanup function rather than turning off the check that found the bug.
What makes an effect 'safe' under Strict Mode?
- It uses no dependencies
- It pairs every setup with a cleanup that fully undoes it (clear timers, abort fetches, unsubscribe)
- It runs only once
- It avoids state
Answer: It pairs every setup with a cleanup that fully undoes it (clear timers, abort fetches, unsubscribe). An effect that cleans up everything it sets up can run twice with no visible difference — which is the whole point of the check.
Is the double-invoke a bug in React?
- Yes, and you should report it
- Only on first mount
- Only in older React versions
- No — it's intentional in development to expose impure renders and missing cleanup
Answer: No — it's intentional in development to expose impure renders and missing cleanup. It's a deliberate development aid. Production runs once; the doubling exists to catch problems before they ship.
Should you rely on the double-invoke behavior in your app logic?
- Yes, design around it
- No — it only happens in dev; production runs once, so don't depend on it
- Yes, for production analytics
- Only for effects, not renders
Answer: No — it only happens in dev; production runs once, so don't depend on it. Expecting doubled behavior in production is a mistake. The extra invocation is a dev-only diagnostic.
How do you opt into Strict Mode?
- Add a flag to package.json
- Install a separate package
- Wrap part of your tree (often the whole app) in <React.StrictMode>
- Set NODE_ENV to strict
Answer: Wrap part of your tree (often the whole app) in <React.StrictMode>. You wrap your component tree in <React.StrictMode> (or <StrictMode>), usually at the root in main.jsx, to enable the dev-only checks.
Continue this course
- Previous: Data Fetching with useEffect
- Next: useLayoutEffect vs useEffect — Measure layout and avoid flicker by running before the browser paints
- Quick reference: React cheat sheet
Frequently asked questions
Why does my effect run twice in development?
Because StrictMode intentionally mounts, unmounts, and remounts components in dev. It runs your effect, runs its cleanup, then runs the effect again to verify your cleanup is correct. It does not do this in production.
Does Strict Mode slow down my production app?
No. All of Strict Mode's extra invocations and warnings are development-only. The production build behaves normally with a single render and effect.
How do I make an effect safe under Strict Mode?
Write a proper cleanup function: cancel timers, abort fetches, and remove listeners. An effect that cleans up fully can run twice with no visible difference, which is the whole point of the check.
Why does it double-invoke my render too?
To surface impure renders. If your component mutates external state or relies on side effects during render, running it twice exposes the bug. Keep render pure: same inputs, same output, no side effects.