Menu

React Strict Mode: Why Effects and Renders Run Twice

StrictMode is a development-only wrapper that renders components twice, runs effects setup, cleanup, setup on mount, and warns about deprecated APIs, so impure renders and missing cleanups show up early. It does nothing in production.

This page includes runnable editors - edit, run, and see output instantly.

<StrictMode> is a React component that turns on extra checks for everything inside it, in development only. It renders each component twice, runs every effect's setup, cleanup and setup again on mount, runs ref callbacks twice, and warns about deprecated APIs. This is why useEffect "runs twice" in development. In a production build it does nothing.

You turn it on by wrapping your app, usually in main.jsx:

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

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

The previews on this page run like a production build, so StrictMode would have no visible effect in them. Instead, each example below has the kind of bug StrictMode exists to catch, and lets you trigger it by hand. Here an effect adds a document click listener and never removes it. Hide and show the panel a couple of times, then click the page.

Each Show adds another listener, and none are removed, so a single click logs once per mount. That is a leak a user could cause by opening and closing a panel. StrictMode would show it on the very first mount: in development it mounts, unmounts and mounts again, so you would see two log lines per click before touching the button.

Effects: setup, cleanup, setup

On mount, StrictMode runs your effect, then its cleanup, then the effect again. It is simulating what happens when a user leaves a screen and comes back, and checking that your cleanup undoes your setup. If it does, the second setup leaves things exactly as one setup would, and you see nothing wrong. If it does not, the bug appears straight away.

The fix for the logger is a cleanup that removes the same function it added:

Toggle as often as you like: each click logs one line while the logger is shown. Under StrictMode in development the console would start with setup, cleanup, setup, and the app would still behave correctly. That sequence is the sign of a healthy effect, not a problem to switch off.

Common effects that need a cleanup: event listeners, setInterval and setTimeout, subscriptions, WebSocket or chat connections, and requests whose response should be ignored if the component is gone. The useEffect page shows the cleanup pattern for each.

Renders: called twice

StrictMode also calls your component function twice for every render (and the functions you pass to useState, useMemo and useReducer, and class constructor, render and shouldComponentUpdate). React keeps the result of one call and throws the other away. A pure component returns the same JSX both times, so nothing changes.

A component that changes something outside itself while rendering is not pure, and calling it twice makes the change happen twice. This one adds a "rendered at" entry to an array it receives as a prop:

Every re-render adds an entry nobody asked for. Here you only notice after clicking. Under StrictMode the first render would already show two new entries instead of one. The fix is to compute during render without mutating: const shown = [...entries, 'extra'].

React 19 adds one detail: during the double render, useMemo and useCallback reuse the result from the first call instead of computing it twice.

Ref callbacks

Since React 19, StrictMode also runs ref callbacks twice on mount: attach, cleanup, attach. A ref callback that adds a listener or registers the node somewhere needs to return a cleanup, exactly like an effect:

<div
    ref={(node) => {
        const observer = new ResizeObserver(onResize);
        observer.observe(node);
        return () => observer.disconnect();
    }}
/>

Deprecated API warnings

StrictMode warns about APIs on their way out, mostly in class components, such as the old UNSAFE_componentWillMount, UNSAFE_componentWillReceiveProps and UNSAFE_componentWillUpdate lifecycles. Some APIs it used to warn about, such as string refs and findDOMNode, were removed from React 19 entirely.

Turning it on for part of the app

<StrictMode> is a normal component, so you can wrap only part of the tree. This helps when adopting it in a large app: start with new screens and expand.

function App() {
    return (
        <>
            <Header />
            <StrictMode>
                <Sidebar />
                <Content />
            </StrictMode>
            <Footer />
        </>
    );
}

Sidebar and Content (and everything they render) get the checks; Header and Footer do not. There is no way to switch it off for a subtree inside a strict part.

Where it is on by default:

  • Vite: the React template wraps <App /> in <StrictMode> in main.jsx.
  • Next.js: the App Router enables it by default; reactStrictMode in next.config.js controls it.

It does nothing in production

A production build ignores <StrictMode>: one render, one effect run, no warnings. So the double calls never cost your users anything, and there is no reason to remove the wrapper before deploying.

If a double effect breaks something in development, such as a request sent twice or an analytics event logged twice, that is information. Most of the time the fix is a cleanup (an ignore flag for a request, a disconnect for a connection). For something that truly must happen once per app load, such as initializing an SDK, run it outside any component at module level, or guard it with a module-level flag.

let didInit = false;

function App() {
    useEffect(() => {
        if (didInit) return;
        didInit = true;
        initAnalytics(); // runs once per page load, even in StrictMode
    }, []);
}

Avoid the common workaround of a ref that skips the second effect run. It hides the warning without fixing the missing cleanup, and the bug comes back the first time the component really unmounts and mounts again, as the logger above does.

Frequently Asked Questions

What does React StrictMode do?

In development only, it renders every component inside it twice, runs each effect's setup, cleanup and setup again when the component mounts, runs ref callbacks twice the same way, and warns about deprecated APIs. The extra calls expose renders that are not pure and effects with missing cleanup.

Why does my useEffect run twice?

Because the component is inside <StrictMode> and you are running a development build. React mounts it, simulates an unmount (your cleanup runs), and mounts it again. If the second setup causes a bug, the effect is missing a cleanup.

Does StrictMode affect production?

No. A production build renders once and runs each effect once, with or without <StrictMode>. You can leave it in your code.

How do I disable StrictMode?

Remove the <StrictMode> wrapper, usually in main.jsx or index.js, or set reactStrictMode: false in next.config.js. Fixing the cleanup is usually better: the double run is pointing at a bug real users can hit.

Why is console.log printed twice?

StrictMode calls your component function twice per render in development. React DevTools can grey out the second call's logs, or hide them, in its settings.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED