← Back

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 StrictMode is and why React uses it.
  • Why useEffect can print the same message twice in development.
  • Where to find StrictMode in a React + TypeScript + Vite project.
  • How StrictMode, useEffect, and useState interact.
  • 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

My React App

Development console

Hello Nikita!

Hello Nikita!

The heading renders once on the page while root StrictMode can run the Effect's development check twice.

Line by line:

  1. import { useEffect } from "react"; makes the Hook available.
  2. App is our React component.
  3. useEffect(function () { ... }, []); tells React to run the Effect after mounting, and not rerun it just because of ordinary state updates.
  4. console.log(...) prints a message to the browser console.
  5. return describes 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:

  1. React renders and commits the component.
  2. React runs the Effect: Hello Nikita! appears.
  3. React performs a test cleanup of the Effect, if a cleanup function exists.
  4. 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

Console after five clicks

START

START

Five clicks update the state and UI, but the empty dependency array prevents ordinary update renders from rerunning the Effect.

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 StrictMode performs 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

Timer example

Console

Tick

Tick

Tick

Cleanup clears the test interval before React sets up the Effect again, so duplicate timers do not remain active.

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

  1. StrictMode adds helpful development checks without rendering HTML.
  2. With root StrictMode, React performs an extra development-only setup and cleanup test for Effects.
  3. For a simple useEffect(..., []), this commonly produces two logs at initial mount in development and one in production.
  4. Changing unrelated state causes a re-render, but not a rerun of an Effect with [].
  5. Keep StrictMode enabled and write Effects that can safely clean up their work.

← Back↑ Back to the top