124

I am new to reactJS and am writing code so that before the data is loaded from DB, it will show loading message, and then after it is loaded, render components with the loaded data. To do this, I am using both useState hook and useEffect hook. Here is the code:

The problem is, useEffect is triggered twice when I check with console.log. The code is thus querying the same data twice, which should be avoided.

Below is the code that I wrote:

import React from 'react';
import './App.css';
import {useState,useEffect} from 'react';
import Postspreview from '../components/Postspreview'

const indexarray=[]; //The array to which the fetched data will be pushed

function Home() {
   const [isLoading,setLoad]=useState(true);
   useEffect(()=>{
      /*
      Query logic to query from DB and push to indexarray
      */
          setLoad(false);  // To indicate that the loading is complete
    })
   },[]);
   if (isLoading===true){
       console.log("Loading");
       return <div>This is loading...</div>
   }
   else {
       console.log("Loaded!"); //This is actually logged twice.
       return (
          <div>
             <div className="posts_preview_columns">
             {indexarray.map(indexarray=>
             <Postspreview
                username={indexarray.username}
                idThumbnail={indexarray.profile_thumbnail}
                nickname={indexarray.nickname}
                postThumbnail={indexarray.photolink}
             />
             )}
            </div>
         </div>  
         );
    }
}

export default Home;

Can someone help me out in understanding why it is called twice, and how to fix the code properly? Thank you very much!

J.Ko
  • 1,909
  • 2
  • 7
  • 18
  • 3
    you say when you check the console.log but there is no console.log – Joe Lloyd Mar 10 '20 at 13:45
  • 1
    Deleted them initially because I pretty much explained what happened, but added them back for clarity as per your comment. – J.Ko Mar 10 '20 at 13:52

9 Answers9

249

Put the console.log inside the useEffect

Probably you have other side effects that cause the component to rerender but the useEffect itself will only be called once. You can see this for sure with the following code.

useEffect(()=>{
      /*
      Query logic
      */
      console.log('i fire once');
},[]);

If the log "i fire once" is triggered more than once it means your issue is one of 3 things.

This component appears more than once in your page

This one should be obvious, your component is in the page a couple of times and each one will mount and run the useEffect

Something higher up the tree is unmounting and remounting

The component is being forced to unmount and remount on its initial render. This could be something like a "key" change happening higher up the tree. you need to go up each level with this useEffect until it renders only once. then you should be able to find the cause or the remount.

React.Strict mode is on

StrictMode renders components twice (on dev but not production) in order to detect any problems with your code and warn you about them (which can be quite useful).

This answer was pointed out by @johnhendirx and written by @rangfu, see link and give him some love if this was your problem.

Joe Lloyd
  • 11,620
  • 7
  • 50
  • 71
  • 60
    One more consideration: Double check if you are using React in strict mode. If so, see here: https://stackoverflow.com/questions/61254372/my-react-component-is-rendering-twice-because-of-strict-mode – jonhendrix Jun 09 '21 at 10:18
  • 2
    In my case, it was unmounting and mounting. I have a dependency in effect on props.searchResult and I am showing results after search finishes but the condition to show results component was '!isSearching && searchDone' and when I clicked to search once again isSearching was set to true (unmounting) then back to false (mounting) but the old result was still in the state and then I set the new result - so the effect was called twice. To fix that I had to set results first and then isSearching to false. Thank you! It wasn't easy for me to notice that. – Adrian Stanisławski Aug 19 '21 at 07:52
  • 2
    In my case StrictMode was causing my useEffect being called twice. thanks for the answer – DRProgrammer May 05 '22 at 01:31
  • 1
    @jonhendrix man... thank you! I almost lost my sanity with that. – Vlad Miller May 13 '22 at 12:51
  • Thanks!!.. you saved my day. When serving build. useeffect works only one time – Ankur Garg May 17 '22 at 11:07
  • @jonhendrix thank you! i wasted 1 hour just for this thing :) – Chirag Joshi May 27 '22 at 06:49
  • thanks, it was StrictMode in my case. – Muhammad Uzair May 29 '22 at 20:28
25

You are most likely checking the issue on a dev environment with strict mode enabled. To validate this is the case, search for <React.StrictMode> tag and remove it, or build for production. The double render issue should be gone. From React official documentation

Strict mode can’t automatically detect side effects for you, but it can help you spot them by making them a little more deterministic. This is done by intentionally double-invoking the following functions:

  • Functions passed to useState, useMemo, or useReducer
  • [...]

Strict Mode - Reactjs docs

Similar question here My React Component is rendering twice because of Strict Mode

rm_
  • 339
  • 4
  • 5
  • This was the issue in my case. Thanks! – Oystein Jun 02 '21 at 07:52
  • But `useEffect` is not on the list of hooks that get double-invoked. In fact, `useEffect` isn't mentioned in the documentation of Strict Mode. In my app it seems like double invoking happens in some cases and not others. – Qwertie Apr 30 '22 at 02:35
15

Remove <React.StrictMode> from index.js This code will be

root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

this

root.render(
    <App />
);

React StrictMode renders components twice on dev server

6

Please check your index.js

  <React.StrictMode>
    <App />
  </React.StrictMode>

Remove the <React.StrictMode> wrapper you should now fire once

root.render(
    <App />
);
azuldev
  • 322
  • 3
  • 9
4

I'm using this as my alternative useFocusEffect. I used nested react navigation stacks like tabs and drawers and refactoring using useEffect doesn't work on me as expected.

import React, { useEffect, useState } from 'react'
import { useFocusEffect } from '@react-navigation/native'

const app = () = {

  const [isloaded, setLoaded] = useState(false)


  useFocusEffect(() => {
      if (!isloaded) {
        console.log('This should called once')

        setLoaded(true)
      }
    return () => {}
  }, [])

}

Also, there's an instance that you navigating twice on the screen.

ßiansor Å. Ålmerol
  • 2,397
  • 2
  • 18
  • 20
2

Not sure why you won't put the result in state, here is an example that calls the effect once so you must have done something in code not posted that makes it render again:

const App = () => {
  const [isLoading, setLoad] = React.useState(true)
  const [data, setData] = React.useState([])
  React.useEffect(() => {
    console.log('in effect')
    fetch('https://jsonplaceholder.typicode.com/todos')
      .then(result => result.json())
      .then(data => {
        setLoad(false)//causes re render
        setData(data)//causes re render
      })
  },[])
  //first log in console, effect happens after render
  console.log('rendering:', data.length, isLoading)
  return <pre>{JSON.stringify(data, undefined, 2)}</pre>
}

//render app
ReactDOM.render(<App />, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

To prevent the extra render you can combine data and loading in one state:

const useIsMounted = () => {
  const isMounted = React.useRef(false);
  React.useEffect(() => {
    isMounted.current = true;
    return () => isMounted.current = false;
  }, []);
  return isMounted;
};


const App = () => {
  const [result, setResult] = React.useState({
    loading: true,
    data: []
  })
  const isMounted = useIsMounted();
  React.useEffect(() => {
    console.log('in effect')
    fetch('https://jsonplaceholder.typicode.com/todos')
      .then(result => result.json())
      .then(data => {
        //before setting state in async function you should
        //  alsways check if the component is still mounted or
        //  react will spit out warnings
        isMounted.current && setResult({ loading: false, data })
      })
  },[isMounted])
  console.log(
    'rendering:',
    result.data.length,
    result.loading
  )
  return (
    <pre>{JSON.stringify(result.data, undefined, 2)}</pre>
  )
}

//render app
ReactDOM.render(<App />, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
HMR
  • 34,271
  • 21
  • 76
  • 150
  • 3
    For someone that is having issues with having mutiple undesire runs of the useEffect hook I DONT recommend adding extra complexity by using a custom hook like that (useIsMounted).He should understand why is that happening and fix it accordingly. – Luis Gurmendez Mar 10 '20 at 14:11
  • @LuisGurmendez Recommending checking if the component is still mounted **before** setting state with **asynchronous result** is sound advice. If op has unexpected calls to the effect then the code posted in the question does not demonstrate that. – HMR Mar 10 '20 at 15:10
  • If that’s the case he can use the return funcion of the useEffect callback to make appropiate cleanups – Luis Gurmendez Mar 10 '20 at 15:12
  • https://es.reactjs.org/blog/2015/12/16/ismounted-antipattern.html – Luis Gurmendez Mar 10 '20 at 15:14
  • @LuisGurmendez Class method `isMounted` has nothing to do with custom hook called `useIsMounted` I could have called that hook anything as long as it starts with `use`. They are completely different things. – HMR Mar 10 '20 at 15:17
  • If you read the document it speaks as components in general, the examples might be using class components but is the same idea. As they say having reference to unmounted components is a code smell. Here is a quote that I find interesting and I hope you too: "Ideally, any callbacks should be canceled in componentWillUnmount, prior to unmounting." – Luis Gurmendez Mar 10 '20 at 15:23
  • @LuisGurmendez There is no reference to a component, there is a reference to a setter created with `useState`, it's a different thing. It is also cleaned up when the xhr promise resolves or rejects this variable is out of scope. The fetch method returns a promise and cannot be cancelled. However; if you have a better solution I'd love to see it. You should probably put it on github since the useIsMounted is there and used many times. – HMR Mar 10 '20 at 15:28
  • There is a reference to the component in the scope of the callback. It is not clean up when the "xhr promise resolves or rejects" that is not the idea of cleanup. The idea is that that request is cancelled, and thus there is no resolved/reject of the promise. And there is actually ways to abort a fetch request, take a look to this post https://stackoverflow.com/questions/31061838/how-do-i-cancel-an-http-fetch-request . – Luis Gurmendez Mar 10 '20 at 15:34
  • The reference is in the scope of the callback as I already said, all the components functions and state are still in memory until the callback ends, that's what the React team says about memory leaks. Because all that memory is taken by a component that will no render anymore. React doesn't do magic, components are javascript functions. Cancelling requests is a good pattern. Fix the cause, not the symptom I see you remove your last comment. this was a response to that. – Luis Gurmendez Mar 10 '20 at 15:58
  • @LuisGurmendez Where is the component then in this callback: `data => { isMounted.current && setResult({ loading: false, data }) }`? I see a function called `setResult` and an object `isMounted` and they will go out of scope as soon as the promise completes. You keep using language that refers to react classes like `all the components functions and state are still in memory` and `component reference in scope` but that doesn't apply here. If the fetch is broken and never completes there is a potential memory leak but if you have to take that into account you may as well not do anything. – HMR Mar 10 '20 at 16:19
2

It is the feature of ReactJS while we use React.StrictMode. StrictMode activates additional checks and warnings for its descendants nodes. Because app should not crash in case of any bad practice in code. We can say StrictMode is a safety check to verify the component twice to detect an error.

You will get this <React.StricyMode> at root of the component.

root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

if you want to restrict components to render twice, You can remove <React.StrictMode> and check it. But It is necessary to use StrictMode to detect a run time error in case of bad code practice.

1

I've had this issue where something like:

const [onChainNFTs, setOnChainNFTs] = useState([]);

would trigger this useEffect twice:

useEffect(() => {
    console.log('do something as initial state of onChainNFTs changed'); // triggered 2 times
}, [onChainNFTs]);

I confirmed that the component MOUNTED ONLY ONCE and setOnChainNFTs was NOT called more than once - so this was not the issue.

I fixed it by converting the initial state of onChainNFTs to null and doing a null check.

e.g.

const [onChainNFTs, setOnChainNFTs] = useState(null);
useEffect(() => {
if (onChainNFTs !== null) {
    console.log('do something as initial state of onChainNFTs changed'); // triggered 1 time!
}
}, [onChainNFTs]);
newbreedofgeek
  • 2,640
  • 1
  • 17
  • 16
1

Here is the custom hook for your purpose. It might help in your case.

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

/**
 * 
 * @param effect 
 * @param dependencies
 * @description Hook to prevent running the useEffect on the first render
 *  
 */
export default function useNoInitialEffect(
  effect: EffectCallback,
  dependancies?: DependencyList
) {
  //Preserving the true by default as initial render cycle
  const initialRender = useRef(true);

  useEffect(() => {
   
    let effectReturns: void | (() => void) = () => {};
    
    /**
     * Updating the ref to false on the first render, causing
     * subsequent render to execute the effect
     * 
     */
    if (initialRender.current) {
      initialRender.current = false;
    } else {
      effectReturns = effect();
    }

    /**
     * Preserving and allowing the Destructor returned by the effect
     * to execute on component unmount and perform cleanup if
     * required.
     * 
     */
    if (effectReturns && typeof effectReturns === 'function') {
      return effectReturns;
    } 
    return undefined;
  }, dependancies);
}

Kiran Maniya
  • 7,224
  • 8
  • 49
  • 71