ReactReact StrictMode

React StrictMode

React.StrictMode is a built-in development tool that helps you find bugs early by intentionally running certain parts of your application twice and warning about patterns that are known to cause problems. It has zero effect in production — it exists solely to surface issues during development.

Think of it as a linter that runs at runtime. Where ESLint catches static problems, StrictMode catches dynamic ones: side effects that run at the wrong time, deprecated APIs, and impure render functions.

Enabling StrictMode

Wrap your application (or any subtree) in <React.StrictMode>. The most common placement is at the root in main.tsx:

TSX
// main.tsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App'

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

You can also wrap individual subtrees — useful when you want to opt a legacy part of the tree out while still protecting the rest:

TSX
function App() {
  return (
    <>
      <StrictMode>
        <NewFeature />   {/* strict checks enabled */}
      </StrictMode>
      <LegacyWidget />   {/* strict checks disabled */}
    </>
  )
}
Note
StrictMode renders nothing in the DOM — it is a purely behavioral wrapper with no visual output.
What StrictMode Actually Does

React StrictMode enables several checks, each targeting a specific category of bug:

Check

What it catches

Double-invoke render

Impure render functions with side effects

Double-invoke state initializers

Side effects in useState/useReducer initial values

Double-invoke reducers

Reducers that mutate state directly

Deprecated lifecycle warnings

componentWillMount, componentWillReceiveProps, componentWillUpdate

Legacy string ref warnings

String refs like ref="myRef"

findDOMNode warnings

Calls to the deprecated ReactDOM.findDOMNode

Double Invocation of Render Functions

The most visible (and often surprising) behavior of StrictMode is that it calls your component function twice during development. React renders the component, discards the output, then renders it again to verify the output is identical.

This is intentional. React requires components to be pure functions — given the same props and state, they must return the same JSX. If your component produces side effects during render (modifying a global variable, calling an API, writing to localStorage), the double invocation will expose the bug.

TSX
// ✗ BAD — side effect inside render
let globalRequestCount = 0

function UserCard({ userId }: { userId: string }) {
  globalRequestCount++   // called TWICE in StrictMode — reveals the bug!
  return <div>User {userId}</div>
}

// ✓ GOOD — move side effects into useEffect
function UserCard({ userId }: { userId: string }) {
  useEffect(() => {
    globalRequestCount++   // runs after render, not during it
  }, [userId])

  return <div>User {userId}</div>
}
Warning
If you see a console.log inside a component fire twice in development, that is StrictMode working correctly. It is NOT a bug — it is exposing that your render function has a side effect.
Double Invocation of State Initializers

StrictMode also double-invokes the initial value function passed to useState and useReducer, as well as the reducer itself. This catches initializers that produce side effects or reducers that mutate state instead of returning a new object.

TSX
// ✗ BAD — initializer with a side effect
function Search() {
  const [results, setResults] = useState(() => {
    // This runs twice in StrictMode — reveals the side effect!
    return fetchResultsSync()   // synchronous network call in initializer
  })
  // ...
}

// ✗ BAD — reducer that mutates state
function counterReducer(state: { count: number }, action: { type: string }) {
  if (action.type === 'increment') {
    state.count++   // mutates the existing object — breaks with double-invoke!
    return state
  }
  return state
}

// ✓ GOOD — pure reducer returning a new object
function counterReducer(state: { count: number }, action: { type: string }) {
  if (action.type === 'increment') {
    return { count: state.count + 1 }   // new object every time
  }
  return state
}
useEffect Cleanup in StrictMode

In React 18+, StrictMode adds another check specifically for effects: it mounts, unmounts, and remounts every component once. This means your useEffect will fire, its cleanup will run, and then it fires again — all in development.

This is designed to catch a real class of bug introduced by React's upcoming Offscreen API, where components may be hidden and re-shown without being destroyed. If your effect is not resilient to being run more than once, it contains a bug.

TSX
// ✗ BAD — effect not properly cleaned up
useEffect(() => {
  const subscription = eventBus.subscribe(handler)
  // forgot to unsubscribe! In StrictMode, two subscriptions pile up.
}, [])

// ✓ GOOD — every setup has a matching teardown
useEffect(() => {
  const subscription = eventBus.subscribe(handler)
  return () => {
    subscription.unsubscribe()   // cleanup runs before the second mount
  }
}, [])

// ✓ GOOD — idempotent effect (safe to run twice)
useEffect(() => {
  document.title = 'My Page'   // setting it twice is harmless
}, [])
Tip
The "double-mount" behaviour is one of the best arguments for always writing cleanup functions in your effects. If you cannot write a cleanup, your effect probably should not run more than once — which usually means it should run somewhere else (e.g., at the module level or triggered by user action, not on mount).
Deprecated Lifecycle Warnings

StrictMode warns about class component lifecycle methods that were deprecated in React 16.3 because they are unsafe to use in concurrent React:

  • componentWillMount → use componentDidMount or move logic to the constructor

  • componentWillReceiveProps → use static getDerivedStateFromProps or componentDidUpdate

  • componentWillUpdate → use getSnapshotBeforeUpdate or componentDidUpdate

These methods were renamed to UNSAFE_componentWillMount etc. to make their danger visible. If you see StrictMode warnings about them, migrate to the safe alternatives or convert the class component to a function component with hooks.

StrictMode Is Not Active in Production

All of the double-invocations and extra warnings are stripped out of production builds by React. Your end users see none of it. StrictMode has no performance cost and no behavioral difference in production — its sole purpose is catching bugs early in development.

TSX
// This is safe to ship — StrictMode does nothing in production bundles.
createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <App />
  </StrictMode>
)

// You do NOT need to remove it before deploying.
Note
Create React App, Vite's React template, and Next.js all enable StrictMode by default. If you disabled it to silence double-render logs, consider re-enabling it — the double renders are exposing real bugs in your components.
Quick Debugging Guide
  • console.log fires twice → render function has a side effect. Move it into useEffect.

  • API called twice on mountfetch is inside the component body, not inside useEffect.

  • useEffect runs twice → expected in StrictMode (React 18+). Write a cleanup function to handle remounts.

  • "Can't perform state update on unmounted component" → effect cleanup is missing; the subscription/timer fires after unmount.

  • Deprecated lifecycle warning → migrate the class component lifecycle method or convert to a function component.