Skip to content
React rc rendering 4 min read

StrictMode

StrictMode is a built-in component that turns on extra development-only checks for the tree inside it. It renders nothing visible and adds no DOM, but it asks React to deliberately run certain code twice and to flag patterns that will break in modern, concurrent React. Crucially, none of this behavior runs in production—it exists purely to surface bugs early, while you are still writing the feature. Treating its warnings as real defects, rather than noise to silence, is one of the cheapest ways to keep an app correct.

Enabling StrictMode

You wrap part of your tree in <StrictMode>. The Vite and Create React App starters wrap your whole app in it by default, but you can apply it to any subtree.

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

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

Everything rendered inside <StrictMode> is checked; everything outside it is not. This lets you adopt strict checks gradually in a large codebase by wrapping one route or feature at a time.

What StrictMode checks

StrictMode performs several independent checks. The most talked-about is intentional double-invocation, but it also warns about legacy and unsafe APIs.

CheckWhat it does
Double renderingRe-runs component bodies, useState/useReducer initializers, and reducer functions an extra time to surface impurity.
Double-mounting effectsMounts each component, runs effects + cleanup, then mounts again to surface missing cleanup.
Deprecated API warningsWarns about legacy string refs, findDOMNode, and unsafe class lifecycle methods.
Outdated context warningsFlags the old legacy context API.

Double-invocation surfaces impure renders

A component’s render should be a pure function of its props, state, and context: given the same inputs, it returns the same JSX and mutates nothing outside itself. To catch violations, StrictMode calls your component function (and state initializers) twice. If rendering is pure, running it twice changes nothing. If it has a side effect, the effect happens twice—and you notice.

let idCounter = 0;

// Impure: mutates a module variable during render.
function Comment({ text }) {
  idCounter += 1; // side effect during render — bug!
  return <li id={`comment-${idCounter}`}>{text}</li>;
}

Under StrictMode the counter jumps by two per render, and the IDs no longer match what you expect. The fix is to keep the work out of render—derive the value from props, or compute it with useId or in an event handler.

import { useId } from "react";

function Comment({ text }) {
  const id = useId(); // stable, render-pure
  return <li id={id}>{text}</li>;
}

Double rendering only happens in development. In production each component renders once, so never rely on the doubling for logic—rely on it only to expose logic that was never safe to begin with.

Double-mounting reveals missing cleanup

An effect that subscribes to something must unsubscribe in its cleanup function, otherwise you leak listeners, timers, or connections across re-mounts. StrictMode stress-tests this by mounting a component, running its effects, immediately running cleanup, then mounting it once more. The lifecycle it simulates is: setup → cleanup → setup.

import { useEffect, useState } from "react";

function Clock() {
  const [now, setNow] = useState(() => new Date());

  useEffect(() => {
    const id = setInterval(() => setNow(new Date()), 1000);
    return () => clearInterval(id); // without this, intervals stack up
  }, []);

  return <time>{now.toLocaleTimeString()}</time>;
}

If you forget the clearInterval cleanup, StrictMode makes the bug obvious: two intervals run at once, so the clock updates twice as fast. With correct cleanup the extra mount is invisible. You can confirm the pattern by logging:

useEffect(() => {
  console.log("connect");
  return () => console.log("disconnect");
}, []);

Output:

connect
disconnect
connect

The matching disconnect between the two connects is exactly what StrictMode is verifying—that your setup is reversible.

It does not run in production

StrictMode is compiled away from production builds. Double-invocation, double-mounting, and the API warnings all disappear, so there is zero runtime cost to shipping it. That means a StrictMode-clean app behaves identically in development and production, while a non-clean one may “work” in dev only because a bug happened to cancel itself out across the two passes.

Console logs you fire during render appear twice in development. React dims the duplicated logs in DevTools so you can tell which were intentional doublings versus your own repeated calls.

Best Practices

  • Keep StrictMode wrapping your whole app and treat its double-render as a permanent test for purity.
  • Never put side effects in a component body or a useState initializer—move them into effects or event handlers.
  • Always return a cleanup function from effects that subscribe, time, or open connections.
  • Make effects idempotent so a setup → cleanup → setup cycle leaves no duplicated state.
  • Don’t disable StrictMode to silence a warning; fix the underlying bug it points to.
  • Use useId for generated IDs instead of mutating counters during render.
Last updated June 14, 2026
Was this helpful?