React StrictMode

StrictMode is a built-in React component that renders no visible UI at all. Instead, wrapping part of your component tree in it turns on a set of extra, development-only checks for every component inside that tree. It exists to catch bugs early — code that accidentally relies on side effects happening during rendering, effects that forget to clean up after themselves, and usage of deprecated or legacy APIs. Because these checks are stripped out of production builds automatically, wrapping your app in <StrictMode> costs nothing for your users but can save you from subtle, hard-to-reproduce bugs before they ever ship.

Overview / How It Works

StrictMode is imported from react and used as a wrapper component: <StrictMode><App /></StrictMode>. It does not add any elements to the DOM — it has no props other than children, and it does not change how anything looks. What it changes is how React behaves while your app runs in development mode.

React function components are supposed to be pure functions of their props and state: given the same inputs, calling the function again should produce the same JSX output, with no side effects (no mutating outside variables, no writing to the DOM, no starting network requests) happening directly in the function body. In real apps this rule gets broken by accident all the time, and the bugs it causes are often invisible until React changes how or when it calls your components — for example, with concurrent features like transitions, Suspense, or offscreen rendering, which may render a component more than once, or render it without committing the result.

StrictMode tries to catch these problems while they’re cheap to fix, by doing the following in development only:

Check What it catches
Double-invokes component render functions Side effects performed directly during render (mutating a variable, writing to the DOM, logging as if it were a side effect)
Double-invokes effects on initial mount (mount → cleanup → mount again) Effects that don’t return a proper cleanup function, or whose cleanup doesn’t fully undo the setup (leaked subscriptions, duplicate timers, double network connections)
Warns about legacy string refs and the legacy context API Old patterns that are deprecated and will eventually be removed
Warns about findDOMNode usage An escape hatch that breaks with some of React’s newer rendering strategies
Warns about unsafe lifecycle methods Class components using UNSAFE_componentWillMount and similar legacy lifecycles

None of this happens in a production build — bundlers detect the production environment and React skips the extra render/effect cycles and warnings entirely, so there is zero runtime cost for your users.

Syntax

import { StrictMode } from "react";

<StrictMode>
  {/* the tree you want extra checks on */}
</StrictMode>
  • StrictMode — imported by name from "react"; it is a wrapper component, not an HTML element.
  • children — any React tree. Everything nested inside opts into the extra development checks.
  • Scope — you can wrap your entire app once at the root, or wrap just one subtree if you want to test or migrate a specific part of your code first.
  • No propsStrictMode takes no configuration props; its behavior is fixed by the React version you’re running.

Examples

Example 1: Wrapping the whole app

import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App";

createRoot(document.getElementById("root")).render(
  <StrictMode>
    <App />
  </StrictMode>
);

This is the standard setup you’ll find in every modern React project scaffold (Vite, Create React App, etc.). It renders nothing extra visually — App renders exactly as it would without StrictMode — but every component inside App now runs under the extra development checks described above.

Example 2: An effect without cleanup

import { useEffect } from "react";

function Logger() {
  useEffect(() => {
    console.log("Effect ran");
  }, []);

  return <p>Check the console.</p>;
}

export default Logger;

Output:

Effect ran
Effect ran

Rendered inside <StrictMode> in development, this component logs "Effect ran" twice on mount, not once. React mounts the component, runs the effect, then immediately simulates an unmount (running any cleanup function, though this one has none) and remounts it, running the effect again. This is intentional and only happens in development — in a production build it logs once. It’s React’s way of asking, “if I mount and unmount you rapidly, does anything break?”

Example 3: An impure render function

import { useState } from "react";

let renderCount = 0; // module-level variable, shared across all instances

function Counter() {
  renderCount++; // side effect happening during render — impure!
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>This module has rendered {renderCount} times</p>
      <button onClick={() => setCount(count + 1)}>Increment: {count}</button>
    </div>
  );
}

export default Counter;

Renders a paragraph showing a render counter and a button. Because renderCount is mutated directly in the component body, StrictMode‘s double-render in development makes the number jump by two on every render instead of one, which is a strong signal that something impure is happening. Without StrictMode this bug would be silent until it caused a real problem, such as the counter being wrong when React re-renders the component extra times during a transition.

How It Works Step by Step / Under the Hood

On initial mount (development, inside StrictMode): React calls your component function to compute JSX, then calls it a second time immediately and discards the second result — it only commits the DOM once. This checks that calling the function twice produces the same output with no visible side effects. Then, once the effects for that component would normally run, React runs them, immediately runs their cleanup functions as if the component had unmounted, and runs the effects again as if it had remounted. Only after that does the component settle into its normal mounted state.

On a state or prop update: the same double-render check applies to every re-render, not just the first one — your render function is called twice per update, with the second result discarded, so purity is checked continuously as the app runs, not just once at startup.

On unmount: a real, final unmount happens exactly once, running your effect cleanup functions one last time, the same as it would without StrictMode.

Common Mistakes

Mistake 1: Treating impure code as “just how render works”

let renderCount = 0;

function Counter() {
  renderCount++; // mutates state outside the component
  return <p>{renderCount}</p>;
}

This mutates a variable that lives outside the component every time the function runs. It happens to “work” without StrictMode, but it breaks the assumption that rendering is a pure calculation, and StrictMode‘s double-render will make the count jump by two instead of one, or seeing a fetch fire twice in development. Move side effects into useEffect, or derive the value with state instead of a module-level mutation:

import { useState, useEffect } from "react";

function Counter() {
  const [renderCount, setRenderCount] = useState(0);

  useEffect(() => {
    setRenderCount((c) => c + 1);
  }, []);

  return <p>{renderCount}</p>;
}

Mistake 2: Effects that never clean up

useEffect(() => {
  window.addEventListener("resize", handleResize);
}, []); // no cleanup returned

Without a cleanup function, every remount adds another listener that’s never removed. Under StrictMode‘s mount-cleanup-remount cycle in development this is usually harmless because the (missing) cleanup just leaves nothing to remove, but in production, if the same component mounts and unmounts repeatedly (navigating between pages, list items being added and removed), listeners pile up and cause memory leaks or duplicate handler calls. Always return a cleanup function that undoes exactly what the setup did:

useEffect(() => {
  window.addEventListener("resize", handleResize);
  return () => window.removeEventListener("resize", handleResize);
}, []);

Best Practices

  • Wrap your whole application in <StrictMode> as early as possible — most project templates already do this in main.jsx or index.jsx.
  • Never disable or remove StrictMode just to make a doubled console log go away — fix the underlying impure render or missing cleanup instead, since the bug is real even if it’s easier to see in development.
  • Every effect that subscribes, connects, or listens should return a cleanup function that fully reverses the setup.
  • Keep component bodies free of side effects: no direct DOM writes, no starting fetches, no mutating variables declared outside the component — do that work inside useEffect or event handlers instead.
  • Pair StrictMode with the eslint-plugin-react-hooks ESLint rules so dependency array and hook-order mistakes are caught even before you run the app.
  • If you’re migrating an older codebase, wrap one subtree at a time instead of the whole app, fix the warnings it surfaces, then expand the wrapped area.

Practice Exercises

1. Create a component whose useEffect logs a message to the console with no cleanup function. Run it inside <StrictMode> and confirm in the browser console that it logs twice on mount. Then add a cleanup function and explain, in a comment, what it undoes.

2. Take a component that increments a variable declared outside the component every time it renders (like the Counter example above). Refactor it to use useState and useEffect so the count only increases by one per real mount, even inside StrictMode.

3. Write a custom hook called useDocumentTitle(title) that sets document.title inside a useEffect. Add a cleanup function that restores the previous title on unmount, and verify with StrictMode‘s mount-unmount-remount cycle that the title ends up correct rather than stuck on a stale value.

Summary

  • StrictMode is a wrapper component from react that renders no UI of its own; it only enables extra checks for development.
  • It has zero effect and zero cost in production builds.
  • On mount and on every re-render, it double-invokes your component’s render function (discarding the extra result) to surface impure rendering.
  • On initial mount, it also runs each effect, cleans it up, and runs it again, to surface effects that don’t clean up properly.
  • It warns about deprecated APIs like string refs, the legacy context API, and findDOMNode.
  • When you see doubled console logs or doubled network calls under StrictMode, treat it as a real bug report, not noise to silence.
  • Wrapping your whole app in <StrictMode> from the start is a cheap way to catch bugs before they reach production or collide with concurrent React features.