Difficulty: Beginner
What does React Strict Mode do? Why does it double-invoke component functions?
React StrictMode is one of those things that confuses almost every React developer at some point, especially the whole double-rendering behavior. Let me clear it all up.
First, the basics: React.StrictMode is a wrapper component that you put around your app (or part of your app) to help catch potential problems during development. The absolutely critical thing to understand is that StrictMode ONLY runs in development. It is completely stripped out in production builds. It does not slow down your production app, it does not change behavior in production, it does not exist in production. Period. This is the number one misconception about StrictMode, and I have seen experienced developers get it wrong.
So what does it actually do? Several things, but the one that trips people up the most is double-invocation. In development, StrictMode intentionally calls your component render functions, useState initializers, useReducer reducers, and useMemo callbacks TWICE. It also, in React 18, mounts your components, unmounts them, and then mounts them again. This sounds insane until you understand why.
The reason is purity checking. React components are supposed to be pure functions: given the same props and state, they should return the same JSX. No side effects during rendering. If your component works correctly when rendered once but breaks when rendered twice, you have a bug. You have a side effect hiding in your render function. Maybe you are incrementing a counter outside the component (let renderCount = 0; renderCount++ in the render body), or mutating an object, or writing to localStorage during render. StrictMode catches these bugs by rendering twice and comparing.
The React 18 mount-unmount-remount cycle is specifically designed to catch missing cleanup in useEffect. Here is the scenario: your useEffect sets up a WebSocket connection. If you do not return a cleanup function that closes the connection, you will leak connections. In production, this might not be obvious until your app has been running for a while and has dozens of zombie connections. StrictMode simulates this by unmounting and remounting, which forces the mount effect to run twice. If your effect does not clean up properly, you will see doubled subscriptions, duplicate event listeners, or other weird behavior immediately in development.
Let me walk through a concrete example of why this matters. Say you have a chat component that subscribes to a WebSocket on mount. Without cleanup, the mount-unmount-remount cycle in StrictMode would create two WebSocket connections. You would see every message twice in your UI. That is StrictMode doing its job -- showing you in development what would eventually become a bug in production when components mount and unmount due to navigation, tab switching, or Suspense boundaries.
The fix is always the same: return a cleanup function from useEffect that undoes whatever the effect set up. Subscribe in the effect, unsubscribe in the cleanup. Add an event listener in the effect, remove it in the cleanup. Start a timer in the effect, clear it in the cleanup.
Another thing StrictMode does is warn about deprecated APIs. If you are using legacy lifecycle methods like componentWillMount, componentWillReceiveProps, or componentWillUpdate, StrictMode will flag them. It also warns about the legacy string ref API and findDOMNode, both of which are being phased out.
A common frustration: developers see their API calls firing twice in development and think something is broken. Their useEffect fetches data, and they see two network requests in the browser DevTools. This is StrictMode at work. The fix is NOT to remove StrictMode. The fix is either to accept that double-fetching in dev is fine (the data is the same both times), or to use a data fetching library like React Query that handles deduplication automatically. React Query will see two requests for the same key and only actually send one.
Both Create React App and Next.js enable StrictMode by default in new projects, so you are probably already running it. You can also wrap just a portion of your app in StrictMode if you want to gradually adopt it.
One more practical note: when working with third-party libraries that are not designed for the double-mount behavior (especially imperative libraries like some chart or map libraries), you might need to use a ref flag pattern. You set a ref to true on mount and check it before initializing the library. But honestly, if a library cannot handle mount-unmount-mount, it probably has other lifecycle bugs too, and you should be aware of that.
import { StrictMode, useState, useEffect } from 'react';
// App.tsx - wraps entire app
function App() {
return (
<StrictMode>
<MyApp />
</StrictMode>
);
}
// This component has a bug that StrictMode surfaces
let renderCount = 0;
function BuggyComponent() {
renderCount++; // Side effect in render - BUG!
console.log('Render count:', renderCount); // 2 in dev, 1 in prod
return <div>Rendered {renderCount} times</div>;
}
// React 18 StrictMode also unmounts/remounts:
function MyComponent() {
useEffect(() => {
console.log('Mounted'); // Called twice in dev (mount, unmount, remount)
const subscription = subscribeToSomething();
return () => {
console.log('Unmounted');
subscription.unsubscribe(); // Must cleanup!
};
}, []);
return <div>Hello</div>;
}
// In dev: Mounted → Unmounted → Mounted
// In prod: Mounted (once)
React 18's StrictMode mounts/unmounts components to verify cleanup works. If your app breaks from this, your useEffect cleanup is missing or incorrect.
StrictMode, Double Invoking, Deprecated APIs, Development