search

Home  >  Q&A  >  body text

How to effectively deal with useEffect running twice in React?

I have a counter and console.log() in useEffect to log every change in my state, but useEffect is not working on the mount is called twice. I'm using React 18. Here is my project's CodeSandbox 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;

P粉759457420P粉759457420404 days ago792

reply all(2)I'll reply

  • P粉156532706

    P粉1565327062023-10-20 11:55:03

    UPDATE: Review this post and be a little wiser and please don't do this.

    Use ref or create 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);
      };
    }

    reply
    0
  • P粉459440991

    P粉4594409912023-10-20 09:39:10

    Since React 18, when you use StrictMode for development, it is normal for

    useEffect to be called twice on mount. Here's what they have in Document:

    This may seem strange, but ultimately, we write better React code by caching HTTP requests and using a cleanup function when there is an issue between two calls, being bug-free, compliant with current guidelines, and compatible with future versions. 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 
    {count}
    ; }; export default Counter;

    In this very detailed article, the React team explains useEffect like never before and illustrates it with examples:

    For your specific use case, you can leave it as is, no worries. And you should not try to use these techniques with useRef and if statements in useEffect to make them fire once, or remove StrictMode , because as you can see in the documentation :

    /* 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();
    }, []);
    
    function TodoList() {
      const todos = useSomeDataFetchingLibraryWithCache(`/api/user/${userId}/todos`);
      // ...

    If you're still having problems, maybe you're using useEffect which, as they say on /learn/synchronizing-with-effects#not-an-effect-initializing-the-application" rel="noreferrer">Not an Effect: Initializing the Application and Not an Effect: Purchase the Product , I recommend you read the article as a whole.

    reply
    0
  • Cancelreply