Skip to content

React Tutorial Advanced React Concepts

React Strict Mode: Why Components Render Twice

Lesson 124 of 156 3 min read By Deepak Paswan Updated Oct 10, 2026

Runnable lesson · press Try it on the examples to edit and run them here.

In a Vite project, src/main.jsx wraps the app in <StrictMode>. It does nothing visible, but it makes React check your code harder in development, and it is the reason you sometimes see logs twice.

App.jsx
JSX
createRoot(document.getElementById('root')).render(
  <StrictMode>
    <App />
  </StrictMode>,
)

What it does (development only)

  1. Renders your components twice. React calls each component function (and useState, useMemo and useReducer callbacks) two times and ignores one result. This finds impure components: ones that change variables outside themselves or depend on randomness during render.
  2. Runs effects one extra time. After the first mount, React simulates unmounting and mounting again: effect, cleanup, effect. This finds missing cleanup.
  3. Runs ref callbacks again to find missing ref cleanup.
  4. Warns about deprecated APIs.

None of this happens in a production build. It is a development tool and it costs you nothing in production.

Seeing the double render

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

let renderCalls = 0;

function Demo() {
  renderCalls++;                       // a side effect during render: impure on purpose
  const [shown, setShown] = React.useState(0);
  return (
    <div>
      <p>Strict Mode demo</p>
      <button onClick={() => setShown(renderCalls)}>Show render calls</button>
      <p>Render calls: {shown}</p>
    </div>
  );
}

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

The first render of Demo runs twice, so the counter shows 2. Remove StrictMode and run it again: it shows 1. Changing renderCalls inside the component is the kind of impurity Strict Mode reveals: the component’s output would depend on how many times React happened to call it.

Rule: rendering must be pure

A component should be a pure function of its props, state and context. Same inputs, same output, no changes to anything outside. If a component behaves strangely in Strict Mode, it is usually breaking this rule:

  • Mutating a variable, array or object defined outside the component.
  • Mutating props or state.
  • Using Math.random() or Date.now() to produce output (generate them in an effect or event handler, or store in state once).
  • Sending a request or writing to the DOM directly during render.

Local mutation is fine: creating an array inside the component and pushing to it before returning is pure, because nothing outside can see it.

The effect double-run

This is the part that confuses people most. A subscription effect without cleanup is visible in Strict Mode:

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

let open = 0;

function Listener() {
  const [shown, setShown] = React.useState("?");

  React.useEffect(() => {
    open++;                     // "subscribe"
    return () => { open--; };   // cleanup: "unsubscribe"
  }, []);

  return (
    <div>
      <button onClick={() => setShown(open)}>Show count</button>
      <p>Subscriptions open: {shown}</p>
    </div>
  );
}

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

The effect runs, is cleaned up and runs again, so the count is 1, correct. If you delete the cleanup line, the count shows 2: a leak that Strict Mode found for you. In production there would be only one run, but the leak would appear in other cases, such as quickly navigating away and back.

“My effect runs twice! How do I stop it?”

Do not. Fix the effect:

  • Fetching: the extra request is harmless if you use an ignore flag or AbortController; the first result is discarded (Fetching Data with Effects).
  • Subscriptions and timers: add the cleanup.
  • One-time actions (analytics “page viewed”, a purchase): these should not live in an effect. Put them in an event handler, or accept that analytics may log twice in development.
  • Never remove StrictMode to hide a bug. It only hides the problem until users find it.

Opting out for part of the tree

Strict Mode applies to everything inside it. You can wrap just a section of the app, or leave it out of the playground (the playground renders App without it). Use it on the whole app in real projects.

Key points

  • Strict Mode runs extra checks in development only: double render, effect setup/cleanup/setup, and deprecation warnings.
  • A component that breaks when rendered twice is impure; an effect that breaks when run twice is missing cleanup.
  • Fix the code instead of removing Strict Mode.