React StrictMode

StrictMode is a development tool that helps you find potential problems in your app. It adds extra checks and warnings in development. It has no effect in production and no performance cost for your users.

How to enable StrictMode

If you created your project with the Vite React template, StrictMode is already enabled in main.jsx:

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

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

You can wrap the entire app (most common) or just specific parts of it. Anything inside <StrictMode> gets the extra checks.

Why useEffect runs twice in development

This surprises almost every React developer the first time they see it.

In React 18, StrictMode deliberately mounts, unmounts, and remounts every component in development. As a result, useEffect runs twice on mount: once on the initial mount, and once after the simulated remount. This only happens in development. In production, effects run once.

Why does React do this? It wants to verify that your effects clean up properly. If mounting twice breaks your app, that is a sign your effect has a side effect that should be cleaned up but is not.

Here is an example that breaks under double-invocation:

// Problem: no cleanup, so this adds a listener on every mount
useEffect(() => {
  window.addEventListener('resize', handleResize);
  // No return, so the old listener is never removed
}, []);

After the simulated remount, two listeners are attached. The fix is to return a cleanup function:

// Correct: cleanup removes the listener before the next mount
useEffect(() => {
  window.addEventListener('resize', handleResize);
  return () => {
    window.removeEventListener('resize', handleResize);
  };
}, []);

With the cleanup in place, the double-invocation in development behaves correctly, and so will production.

What StrictMode checks for

  • Impure render functions: React calls render functions twice in development to catch components that produce different output on repeated renders. Pure renders (same input, same output) pass this test without issue.
  • Missing effect cleanups: the double-mount test described above exposes effects that do not clean up after themselves.
  • Deprecated APIs: StrictMode logs a warning if you use APIs that are scheduled for removal, giving you time to migrate before they disappear.
  • Legacy patterns: old string refs, findDOMNode, and other patterns that no longer work with concurrent React trigger warnings.

Should you disable StrictMode?

Almost never. The double useEffect is the most common reason people consider disabling it, but that is the wrong response.

If an API call fires twice and you see it in the network tab, the correct fix is to add a cleanup using AbortController:

useEffect(() => {
  const controller = new AbortController();

  fetch('/api/data', { signal: controller.signal })
    .then((res) => res.json())
    .then((data) => setData(data))
    .catch((err) => {
      if (err.name !== 'AbortError') {
        setError(err.message);
      }
    });

  return () => controller.abort();
}, []);

When StrictMode simulates the remount, the cleanup aborts the first request before the second one starts. In production only one request runs. The code is also more correct: if the component unmounts before the fetch completes, the request is cancelled instead of updating state on an unmounted component.

Disabling StrictMode to suppress the double-invoke hides a real cleanup problem rather than fixing it.

StrictMode in production

StrictMode is stripped from production builds entirely. The double-invoke behaviour, the extra warnings, and the repeated renders all disappear. Your users see the same app they would see without StrictMode.

Common mistakes

Turning off StrictMode to avoid the double useEffect. The correct response is to add a cleanup function or rethink whether the effect belongs in useEffect at all. Turning off StrictMode just hides the issue.

Thinking the double-mount behaviour means something is broken. It is intentional. React is running a test, not misfiring.

Assuming effects that work without StrictMode will work in production. They often will, but they have a latent cleanup problem that will surface as a memory leak or stale state update once the component is mounted and unmounted by normal navigation.