Why useEffect running twice and how to handle it well in React?

I have a counter and a console.log() in an useEffect to log every change in my state, but the useEffect is getting called two times on mount. I am using React 18. Here is a CodeSandbox of my project and the code below:

import  { useState, useEffect } from "react";

const Counter = () => {
  const [count, setCount] = useState(5);

  useEffect(() => {
    console.log("rendered", count);
  }, [count]);

  return (
    <div>
      <h1> Counter </h1>
      <div> {count} </div>
      <button onClick={() => setCount(count + 1)}> click to increase </button>
    </div>
  );
};

export default Counter;

0

useEffect being called twice on mount is normal since React version 18 when you are in development with StrictMode. Here is an overview of the reason from the doc:

In the future, we’d like to add a feature that allows React to add and remove sections of the UI while preserving state. For example, when a user tabs away from a screen and back, React should be able to immediately show the previous screen. To do this, React will support remounting trees using the same component state used before unmounting.

This feature will give React better performance out-of-the-box, but requires components to be resilient to effects being mounted and destroyed multiple times. Most effects will work without any changes, but some effects do not properly clean up subscriptions in the destroy callback, or implicitly assume they are only mounted or destroyed once.

To help surface these issues, React 18 introduces a new development-only check to Strict Mode. This new check will automatically unmount and remount every component, whenever a component mounts for the first time, restoring the previous state on the second mount.

This only applies to development mode, production behavior is unchanged.

It seems weird, but in the end, it’s so we write better React code, bug-free, aligned with current guidelines, and compatible with future versions, by caching HTTP requests, and using the cleanup function whenever having two calls is an issue. Here is an example:

/* Having a setInterval inside an useEffect: */

import { useEffect, useState } from "react";

const Counter = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const id = setInterval(() => setCount((count) => count + 1), 1000);

    /* 
       Make sure I clear the interval when the component is unmounted,
       otherwise, I get weird behavior with StrictMode, 
       helps prevent memory leak issues.
    */
    return () => clearInterval(id);
  }, []);

  return <div>{count}</div>;
};

export default Counter;

In this very detailed article, React team explains useEffect as never before and says about an example:

This illustrates that if remounting breaks the logic of your application, this usually uncovers existing bugs. From the user’s perspective, visiting a page shouldn’t be different from visiting it, clicking a link, and then pressing Back. React verifies that your components don’t break this principle by remounting them once in development.

For your specific use case, you can leave it as it’s without any concern. And you shouldn’t try to use those technics with useRef and if statements in useEffect to make it fire once, or remove StrictMode, because as you can read on the doc:

React intentionally remounts your components in development to help you find bugs. The right question isn’t “how to run an Effect once”, but “how to fix my Effect so that it works after remounting”.

Usually, the answer is to implement the cleanup function. The cleanup function should stop or undo whatever the Effect was doing. The rule of thumb is that the user shouldn’t be able to distinguish between the Effect running once (as in production) and a setup → cleanup → setup sequence (as you’d see in development).

/* As a second example, an API call inside an useEffect with fetch: */

useEffect(() => {
  const abortController = new AbortController();

  const fetchUser = async () => {
    try {
      const res = await fetch("/api/user/", {
        signal: abortController.signal,
      });
      const data = await res.json();
    } catch (error) {
      // ℹ️: The error name is "CanceledError" for Axios.
      if (error.name !== "AbortError") {
        /* Logic for non-aborted error handling goes here. */
      }
    }
  };

  fetchUser();

  /* 
    Abort the request as it isn't needed anymore, the component being 
    unmounted. It helps avoid, among other things, the well-known "can't
    perform a React state update on an unmounted component" warning.
  */
  return () => abortController.abort();
}, []);

You can’t “undo” a network request that already happened, but your cleanup function should ensure that the fetch that’s not relevant anymore does not keep affecting your application.

In development, you will see two fetches in the Network tab. There is nothing wrong with that. With the approach above, the first Effect will immediately get cleaned… So even though there is an extra request, it won’t affect the state thanks to the abort.

In production, there will only be one request. If the second request in development is bothering you, the best approach is to use a solution that deduplicates requests and caches their responses between components:

function TodoList() {
  const todos = useSomeDataFetchingLibraryWithCache(`/api/user/${userId}/todos`);
  // ...

And if you are still having issues, maybe you are using useEffect where you shouldn’t be in the first place, as they say on Not an Effect: Initializing the application , and Not an Effect: Buying a product. I would suggest you read the article as a whole.

12

While I agree with the points raised in the accepted answer,

If one still needs to use useEffect

Then, using the useRef() to control the flow is an option.

To apply the effect ONLY on the FIRST mount:

const effectRan = useRef(false);

useEffect(() => {
  if (!effectRan.current) {
    console.log("effect applied - only on the FIRST mount");
  }

  return () => effectRan.current = true;
}, []);

To apply the effect on the REmount:

const effectRan = useRef(false);

useEffect(() => {
  if (effectRan.current || process.env.NODE_ENV !== "development") {
    console.log("effect applied - on the REmount");
  }

  return () => effectRan.current = true;
}, []);

When is this useful?

One application is where the useEffect contains a server request that can potentially change the state of the backend (e.g. DB change). In that case, unintended (duplicate) server requests due to StrictMode can lead to unforeseen results.

Can’t we cancel the request via AbortController?

Yes, we can cancel a request. However, by the time the cancel gets invoked, the request might have already run to completion, if not altered number of backend states. Abort does not guarantee a sound rest, at least not out of the box. Therefore, (I think) a request that is NOT intended; should not be attempted in the very first place.

1

2024 UPDATE (React 19+):

You can’t/shouldn’t try to work around this with the useRef hack mentioned in other answers, as it no longer works in React 19+. The best you can do is either:

  1. Live with this behavior (see accepted answer) since it’s intended to expose bugs in production, or
  2. Disable strict mode.

Many of the older answers here suggested using useRef to run effect logic only once. The only reason this ever worked is because “refs not getting in strict mode” was a known bug since 2022, and it is getting fixed in React 19:

  • Bug: React 18 Strict mode does not simulate unsetting and re-setting DOM refs #24670
  • React 18 strict mode docs are inconsistent about how unmount and remount actually works (and whether it’s simulated) #6123

2023-08-10: [F]or refs specifically, we intend to update Strict Mode to also destroy and re-create refs during the simulated unmount/remount, since this is the behavior that will happen in production features: facebook/react#25049. We’re still rolling this change out and I don’t have a timeline for when it will land, but the idea is that we’re simulating an actual unmount/remount.

At the time of this writing, there is an open pull request in the react.dev docs repo to mention that React 19 will properly destroy and recreate refs in Strict Mode: Add information about ref handling in strict mode #6777 :

In React 19, React will run an extra setup+cleanup cycle in development for refs to components, much like it does for Effects.
React will detach refs to components that were created via useRef by setting ref.current to null before setting it to the DOM node or handle.
For ref callbacks, React will call the callback function with the DOM node or handle as its argument. It will then call the callback’s cleanup function before calling the ref callback function again with the DOM node as its argument.
You can read more about refs in Manipulating the DOM with Refs.

1

Update: Looking back at this post, slightly wiser, please do not do this.

Use a ref or make a custom hook without one.

export const useClassicEffect = createClassicEffectHook();

function createClassicEffectHook() {
  if (import.meta.env.PROD) return React.useEffect;

  return (effect: React.EffectCallback, deps?: React.DependencyList) => {
    React.useEffect(() => {
      let isMounted = true;
      let unmount: void | (() => void);

      queueMicrotask(() => {
        if (isMounted) unmount = effect();
      });

      return () => {
        isMounted = false;
        unmount?.();
      };
    }, deps);
  };
}

1

I had a use case in which useEffect runs to persist data in the database using next-auth provider “so I don’t really need the purpose of strict mode it in this case”, and even AbortController() didn’t help

You can’t “undo” a network request that already happened

So the data is persisted twice, I had to fix it this way, using ref :

const count = useRef(0);
useEffect(() => {
  if (count.current !== 0) {
    // code
  }
  count.current++;
}, [])

This will make the code inside useEffect only running the second time.

NOTE: do not use this in production since there is no strict mode there, so the code inside useEffect will not run at all


update:

As it is mentioned in @YoussoufOumar’s comment below

you shouldn’t put a logic that persist into database in an Effect
but instead you should put it inside a function that runs when an event is happening ( button clicked for example ) otherwise the code will be executed each time the component is mounted.

However, in my case, there is no such an event the component works like a listner and it mounts only once.

1

This is what I have been using to get around this issue. Yes it will force an extra state change, but I would rather use this simpler solution to avoid having to cancel the use effects I only need to call once (api or otherwise) when trying to debug other problems.

I had a more complex solution that was canceling request and trying to detect the “fake” dev only call but it was causing errors on some of my pages that made it harder to debug in general so using the KISS rule here. (even more so because of all the possible ‘async’ness with this problem)

export const useEffectOnce = ( effect )=> {
    const [needToCall, setNeedToCall] = React.useState(false);

    React.useEffect(()=> {
        if (needToCall) {
            effect();
        }
        else {
            setNeedToCall(true);
        }
    }, [needToCall]);
};

I just dropped this method in my common util file and call it as you would a normal useEffect as before

useEffectOnce(() => {
  // your code here you want to run once even in strict dev
})

you can add this hook to run it only once:

import { DependencyList, EffectCallback, useEffect, useRef } from "react";

const useEffectAfterMount = (cb: EffectCallback, dependencies: DependencyList | undefined) => {
  const mounted = useRef(false);

  useEffect(() => {
    if (mounted.current) {
      return cb();
    }
    mounted.current = true;
  }, dependencies);
};

export default useEffectAfterMount;

and use it:

  useEffectAfterMount(() => {
   ...
  }, []);

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật