React.js + Next.js · Module 3. Forms and Effects · Lesson 11 · Point 11
React StrictMode and Why Effects Run Twice
React + TypeScript · GoIT Dev Notes
In this lesson, you will learn:
- What
StrictModeis and why React uses it. - Why
useEffectcan print the same message twice in development. - Where to find
StrictModein a React + TypeScript + Vite project. - How
StrictMode,useEffect, anduseStateinteract. - The difference between development and production.
- What to do when an Effect unexpectedly runs twice.
REACT · 01
1. Understand What StrictMode Is
StrictMode is a React component that enables extra checks during development. It helps developers notice potential problems before users encounter them. It does not create a visible HTML element.
Think of it as a safety inspector: the inspector does not become part of your product, but checks whether the process works reliably.
REACT · 02
2. See the Same Effect Message Twice
File: src/App.tsx
import { useEffect } from "react";
export default function App() {
useEffect(function () {
// Print a message after the component mounts.
console.log("Hello Nikita!");
}, []);
return <h1>My React App</h1>;
}
Browser Result
Line by line:
import { useEffect } from "react";makes the Hook available.Appis our React component.useEffect(function () { ... }, []);tells React to run the Effect after mounting, and not rerun it just because of ordinary state updates.console.log(...)prints a message to the browser console.returndescribes what React displays on the page.
You might expect one message. But with StrictMode enabled at the root in development, you will typically see:
Hello Nikita!
Hello Nikita!
This does not, by itself, mean that your code is broken.
REACT · 03
3. Find StrictMode in main.tsx
Look in src/main.tsx:
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
// Find the HTML element where React will render the app.
const rootElement = document.getElementById("root")!;
// Create the React root and render App inside StrictMode.
createRoot(rootElement).render(
<StrictMode>
<App />
</StrictMode>
);
The ! after getElementById("root") tells TypeScript that we expect the element to exist. The Vite HTML template normally includes <div id="root"></div>.
The important part is:
<StrictMode>
<App />
</StrictMode>
This enables development checks for the component tree inside StrictMode.
REACT · 04
4. Follow the Extra Effect Check
When StrictMode wraps the application root, React performs an extra development-only setup and cleanup cycle for Effects after the initial mount.
For our simple logging Effect, the sequence is:
- React renders and commits the component.
- React runs the Effect:
Hello Nikita!appears. - React performs a test cleanup of the Effect, if a cleanup function exists.
- React runs the Effect again:
Hello Nikita!appears again.
GoIT describes this as mounting, unmounting, and mounting again. That is a useful beginner mental model, but it is not necessarily a literal removal and recreation of the DOM elements. More precisely, React tests the Effect's setup and cleanup behavior.
In development, StrictMode may also call the component's render function twice to detect impure rendering. Rendering and running an Effect are different things.
REACT · 05
5. Compare Development and Production
In a production build, React does not perform this additional StrictMode test.
For this Effect:
useEffect(function () {
console.log("READY");
}, []);
This table assumes the component remains mounted, the code is unchanged, and there are no other Effect triggers such as a development hot reload.
| Environment | Initial READY messages |
After an ordinary state update |
|---|---|---|
Development with root StrictMode |
Usually 2 | Still 2 |
Development without root StrictMode |
1 | Still 1 |
| Production | 1 | Still 1 |
Important: useEffect still works in production. Only the extra development checks are removed. An Effect can run again if the component genuinely unmounts and remounts.
REACT · 06
6. Combine StrictMode, useEffect, and useState
File: src/App.tsx
import { useEffect, useState } from "react";
export default function App() {
// Store the number of button clicks.
const [clicks, setClicks] = useState(0);
useEffect(function () {
// With StrictMode at the root, this normally logs twice initially in development.
console.log("START");
}, []);
function handleClick() {
// Update the state and request another render.
setClicks(clicks + 1);
}
return (
<button onClick={handleClick}>
You clicked {clicks} times
</button>
);
}
Browser Result
At startup, START usually appears twice in development with root StrictMode.
Clicking the button updates clicks and causes React to render again, but the Effect does not run again because it has an empty dependency array [].
REACT · 07
7. Understand What an Empty Dependency Array Means
An empty dependency array does not mean “this code can only ever run once.”
useEffect(function () {
console.log("READY");
}, []);
It means that ordinary updates of the currently mounted component do not rerun this Effect because it has no changing dependencies.
The Effect can still run again when:
- the component genuinely unmounts and mounts again;
- root
StrictModeperforms its extra development check; - development hot reload replaces the component code.
REACT · 08
8. Do Not Remove StrictMode to Hide a Problem
Should you remove StrictMode because an Effect runs twice? Usually, no.
If an Effect runs twice, investigate whether it performs setup safely and can clean up after itself. Removing StrictMode only hides the development check; it does not fix underlying problems.
This version removes root StrictMode:
// src/main.tsx
createRoot(document.getElementById("root")!).render(
<App />
);
It may make a log appear once, but the checks can help reveal real bugs.
REACT · 09
9. Keep StrictMode Enabled at the Root
The recommended development setup keeps the component tree inside StrictMode:
// src/main.tsx
createRoot(document.getElementById("root")!).render(
<StrictMode>
<App />
</StrictMode>
);
The checks do not affect the production build. They help you find components whose rendering is impure or whose Effects are missing cleanup.
REACT · 10
10. Clean Up Work Started by an Effect
Sometimes an Effect starts something that must be stopped when the Effect finishes. For example, an interval must be cleared.
File: src/App.tsx
import { useEffect } from "react";
export default function App() {
useEffect(function () {
// Start a timer that logs once every second.
const intervalId = window.setInterval(function () {
console.log("Tick");
}, 1000);
// Stop the timer before the Effect is set up again or the component unmounts.
return function () {
window.clearInterval(intervalId);
};
}, []);
return <h1>Timer example</h1>;
}
Browser Result
Here, return function () { ... } is the cleanup function. This example previews a later topic: proper Effect cleanup.
In development, React can test setup, cleanup, and setup again without leaving two intervals running.
REACT · 11
11. Correct Common StrictMode Mistakes
Mistake: “Two logs always mean a bug.”
Correction: Two initial Effect logs can be expected with root StrictMode in development.
Mistake: “An empty dependency array means the Effect can never run again.”
Correction: [] prevents reruns caused by ordinary updates of that mounted component. A real remount runs the Effect again; development checks can also add an extra cycle.
Mistake: “StrictMode doubles all button clicks.”
Correction: StrictMode does not call event handlers twice merely because it is enabled.
Mistake: “useEffect does not work in production.”
Correction: It works normally in production. The extra StrictMode test is development-only.
REACT · 12
12. Check Yourself and Summarize
Question: An Effect has []. With root StrictMode enabled, development shows two START messages. You click a state-changing button five times. How many START messages are there in total?
Answer: Normally two, because the clicks do not rerun this Effect.
Summary
StrictModeadds helpful development checks without rendering HTML.- With root
StrictMode, React performs an extra development-only setup and cleanup test for Effects. - For a simple
useEffect(..., []), this commonly produces two logs at initial mount in development and one in production. - Changing unrelated state causes a re-render, but not a rerun of an Effect with
[]. - Keep
StrictModeenabled and write Effects that can safely clean up their work.