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

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)); // 15

2️⃣ 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 -> subscribe

3️⃣ 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 balanced

Common Errors (and the fix)

📋 Quick Reference

AspectBehavior
Wrapper<React.StrictMode>
Render in devinvoked twice (must be pure)
Effect in devrun, cleanup, run again
Productionsingle render & effect
The fixpure 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

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

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.

Related lessons