DesignDev.io logoDesignDev.io logo

  1. Home
  2. /React Foundations
  3. /Strict Mode in React 18: What It Breaks and Why That's Good
← Back to home

Strict Mode in React 18: What It Breaks and Why That's Good

Alex Chen

Alex Chen

Cover Image for Strict Mode in React 18: What It Breaks and Why That's Good
Alex Chen

Alex Chen

January 22, 2026·5 min read
Updated April 29, 2026
Series:React Foundations
Level:Intermediate
Tags:reactweb
Assumes:Comfortable with React hooks, useEffect, and component lifecycle

React 18's strict mode double-invokes effects by design. Most developers disable it when it starts breaking things. That's the wrong response — the break is a signal, not a false alarm. Here's what strict mode actually does and why every bug it surfaces is a real bug.


At some point in a React 18 project, you've probably seen this: an effect that runs twice, a subscription that stacks, a timer that fires double, an animation that plays twice on mount. You check the code. It looks fine. You check the docs. You find that React 18 strict mode intentionally double-invokes effects in development.

The common response: add // eslint-disable or switch to useLayoutEffect or, in the most drastic cases, remove <StrictMode> from the app entirely.

All of these are wrong responses. The double invocation isn't a quirk to work around — it's a detector for a real category of bugs. If your effect breaks when React mounts and unmounts it once, it will break in production too, just less predictably.


What strict mode does in React 18

<StrictMode> is a development-only tool. It has no effect in production builds.

In React 18 development mode, strict mode does three things:

1. Double-invokes render functions. Your component renders twice. The second render is thrown away — but if your render has side effects (it shouldn't), this surfaces them.

2. Double-invokes state initializers and reducers. The function passed to useState and useReducer runs twice. If your initializer has side effects, this surfaces them.

3. Mounts, unmounts, and remounts every component once. This is the new behaviour in React 18. Effects run, cleanup runs, effects run again. The final state is a component that has been mounted twice and cleaned up once — as if the component appeared, disappeared, and appeared again.

The third behaviour is the one that causes the visible breakage.


Why React does this

React 18 introduced concurrent features — useTransition, useDeferredValue, and the upcoming ability to serialise and restore component state for performance. In concurrent React, the runtime can mount a component, unmount it, and remount it later to restore state. This might happen when:

  • A component is offscreen but might be shown again
  • React needs to pause and discard work to handle a more urgent update
  • Future features like Activity (the API previously called Offscreen) restore cached component trees

Strict mode's double-mount simulates this behaviour in development so you find the bugs before they appear in production under concurrent rendering.

The guarantee React is asking you to uphold: effects must be idempotent across mount → unmount → remount cycles. The cleanup function must fully reverse what the setup function did. If it doesn't, the second mount leaves the component in a broken state.


The bugs strict mode surfaces

Subscriptions that stack

In production, single-mount React: one listener added, works fine. In strict mode: listener added, component unmounts (cleanup runs — but there's no cleanup), component mounts again, second listener added. Now every app:notification event triggers the handler twice.

Fix: always return a cleanup function that removes what you added.

Strict mode's double invocation runs setup, cleanup, setup. You end up with exactly one listener — the same state as a single mount. The effect is idempotent.

Timers that double-fire

In strict mode: interval starts, unmount (no cleanup — interval still running), remount, second interval starts. Now setSeconds fires twice per second. State increments at double speed.

Connections that open twice

In strict mode: connection opens, unmount (no cleanup — connection still open), remount, second connection opens. Now you have two WebSocket connections. Messages arrive twice. Events fire twice.


The effects that appear to break but are actually fine

Some effects look broken under strict mode but aren't — they just look different.

Fetch requests that fire twice

In strict mode: fetch fires, response arrives (or is abandoned), cleanup runs (nothing to clean up), fetch fires again. You see two network requests in the dev tools.

This isn't breaking your app. The second fetch replaces the first. The data is correct. The appearance of two requests is informational — it's showing you that your fetch has no cancellation mechanism, which in production could cause a race condition if the component unmounts before the response arrives.

The right response is not to remove strict mode — it's to add cancellation:

Now strict mode's double-invocation: fetch starts, cleanup aborts it, fetch starts again. One request in flight. The race condition Alex described in Article #1 is handled.

Analytics events that fire twice

This one is genuinely awkward. You don't want two page view events in development. The right solution: either accept the duplication in development and filter it in your analytics dashboard, or wrap the call in a ref to ensure it only fires once:

Or use a useRef that persists across the double-mount:


Reading strict mode output in React DevTools

React DevTools 4.18+ shows which renders are from strict mode's double-invocation. In the Profiler, strict mode renders are dimmed — you can distinguish "real" renders from strict mode validation renders.

In the Components tab, components mounted under strict mode show a small indicator. When debugging, this helps you distinguish "this rendered because state changed" from "this rendered because strict mode is checking it."


The three wrong responses

Disabling strict mode. The most common mistake. The bugs that strict mode surfaces will appear in production under concurrent React features. You've silenced the detector without fixing the problem.

Switching to useLayoutEffect to avoid double-invocation. useLayoutEffect is also double-invoked in strict mode. This doesn't solve anything.

Adding a useRef guard to prevent the second setup from running. This defeats the purpose of the check. If your effect can't run twice safely, it can't run under concurrent remounting safely — and you've just hidden the bug.


The one right response

Fix the cleanup function. Every setup has a corresponding teardown. That's the contract.

If you can't write a cleanup function — if the operation is truly one-way and irreversible — that operation probably belongs in an event handler, not a useEffect. Effects are for synchronisation. Event handlers are for one-time imperatives.


What to do when you inherit a codebase with strict mode removed

Put it back. Run the app in development. Fix every effect that breaks. It will surface real bugs — subscriptions that leak, timers that stack, connections that accumulate. Each one is a bug you'd rather find in development than in a bug report.

The time investment is front-loaded. Once effects are written correctly, strict mode is silent. It only makes noise when there's something worth making noise about.


Up next: How to build a component library with proper versioning — the final article in the React Foundations series, covering the step from building components to distributing them.

Related: useEffect, What Is It Really? by Muhammad Athar — the companion explainer that covers cleanup functions in depth, which is the fix for every strict mode violation.

Alex Chen is a senior frontend engineer who writes about React patterns, JavaScript internals, and the decisions that separate maintainable codebases from ones that fight back. Opinionated by design.

Photo by Kitty Hutchinson on Unsplash


More Stuff

Publishing Your First React Component to npm — the Complete Setup
April 1, 2026·3 min read

Publishing Your First React Component to npm — the Complete Setup

Most tutorials stop at "it works on my machine." This one doesn't. Publishing to npm is where your library becomes real — installable, versioned, and usable by anyone. Here's the complete setup, including the fields most developers get wrong in package.json and how to inspect what you're actually shipping before it goes live.

Muhammad Athar

Muhammad Athar

How to structure a UI library that scales — folders, exports, and naming conventions
April 1, 2026·3 min read

How to structure a UI library that scales — folders, exports, and naming conventions

The folder structure you pick on day one is the one you'll live with across 40 components. Get it wrong and you'll be fighting your own codebase by article 10. Get it right and adding a new component takes five minutes and follows a pattern every contributor can learn in one sitting.

Muhammad Athar

Muhammad Athar

DesignDev.io logoDesignDev.io logo

Explore

  • Home
  • Search
  • Authors
  • Series
  • About Us

Account

  • Sign in

Legal

  • Privacy Policy
  • Terms of Service

© 2026 DesignDev.io · All rights reserved

Built with Next.js · Tailwind · Sanity · Vercel