25
votes


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!

5
you say when you check the console.log but there is no console.logJoe Lloyd
Deleted them initially because I pretty much explained what happened, but added them back for clarity as per your comment.J.Ko

5 Answers

38
votes

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 2 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.

3
votes

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.

2
votes

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>
2
votes

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

0
votes

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]);