Troubleshooting & Comparisons

Why useEffect Runs Twice in React StrictMode

Understand why useEffect runs twice in React 18 StrictMode and explore reliable solutions.

3 min read

React 18 introduced intentional changes that caused useEffect to run twice in development when StrictMode is enabled. This behavior is designed to surface potential issues in your code. While frustrating for some, understanding the cause and applying reliable fixes can ensure your applications operate smoothly without compromising future-proofing.

Symptom: useEffect Runs Twice in Development

Developers upgrading to React 18 often notice that useEffect executes twice in development mode when using an empty dependency array ([]). This behavior is specific to StrictMode and does not affect production builds. For instance:

jsx
import React, { useEffect } from "react";

export default function App() {
  useEffect(() => {
    console.log("Effect ran");
  }, []);
  
  return <p>Hello, world!</p>;
}

In React 18 with StrictMode enabled, the console outputs:

Effect ran
Effect ran

This can disrupt logic dependent on effects running once, such as data fetching.

Cause: StrictMode's Development-only Check

React 18 introduced a development-only feature in StrictMode where components are intentionally mounted, unmounted, and remounted. This check aims to uncover issues like unhandled side-effects or missing cleanup logic in components.

For example:

  • On the first mount, useEffect runs.
  • React immediately unmounts the component to trigger cleanup logic.
  • The component is remounted, causing useEffect to run a second time.

React's rationale includes:

  • Preparing for future features like state preservation during unmounts.
  • Providing developers with tools to improve code reliability.

Fix: Conditional Execution Based on useRef

To avoid unintended side-effects during this double execution, you can use a useRef to conditionally run useEffect logic. Here's how:

steps

  1. Import the necessary hooks:

    jsx
    import { useEffect, useRef } from "react";
  2. Initialize a useRef to track if the effect has run:

    jsx
    const hasEffectRun = useRef(false);
  3. Conditionally execute the effect code:

    jsx
    useEffect(() => {
      if (!hasEffectRun.current) {
        console.log("Effect runs once");
        hasEffectRun.current = true;
      }
    }, []);

This ensures the effect logic runs only once during the StrictMode's double-render cycle.

Implementation Guide: Handling Dual Execution Variations

The solution can be adapted based on when you prefer the effect logic to run during the StrictMode checks.

comparison

Variation 1: Execute on the first render:

jsx
useEffect(() => {
  if (!hasEffectRun.current) {
    console.log("Run during the first effect execution");
    hasEffectRun.current = true;
  }
}, []);

Variation 2: Execute on the second (stable) render:

jsx
useEffect(() => {
  if (hasEffectRun.current) {
    console.log("Run only on the stable (second) execution");
  } else {
    hasEffectRun.current = true;
  }
}, []);

Choose a variation based on your application's needs.

Takeaway: Adopt Best Practices without Disabling StrictMode

React 18's StrictMode changes highlight potential shortcomings in effect cleanup and side-effect handling. While it might seem tempting to disable StrictMode, doing so conceals real issues and diminishes forward compatibility.

Instead:

  • Use recommended patterns like useRef to mitigate double execution.
  • Embrace StrictMode as a tool for robust, future-ready applications.
  • Avoid quick fixes that lead to long-term maintenance headaches.

FAQ

Why does useEffect run twice in React StrictMode?

useEffect runs twice in React 18 StrictMode during development due to an intentional remounting cycle. This tests cleanup functions and surfaces potential bugs.

Is it safe to disable StrictMode to fix the issue?

No, disabling StrictMode conceals real bugs and removes safety checks, reducing your application's reliability. Prefer using solutions like useRef.

How does useRef help mitigate useEffect double renders?

useRef provides a persistent value across renders. By tracking when useEffect has run, you can conditionally execute logic to prevent duplicate operations.


Official reference: React documentation.