react hook useEffect infinite loop despite dependency list

I've been trying to get my head around hooks, in particular useEffect and its dependency list, but for some reason, the following code keeps running endlessly. I've found this very relevant question but haven't been able to make my code work following the suggestion there. Here is the buggy code, you may have to enable your browser console to see the loop.

// a fake fetch for the sake of the example
function my_fetch(f, delay) {
  setTimeout(f, delay)
}

// if account is not loaded then useEffect should not fetch the balance
function useFetchBalance(account_id, account_loaded) {

  const [balance, setBalance] = useState(null)

  useEffect(() => {
    if (!account_loaded) return; // only fetch if dependency is resolved
    my_fetch(() => setBalance(41 + account_id), 3000)
  }, [account_id, account_loaded])

  return balance
}


function App() {

  const [account, setAccount] = useState({ id: null, loaded: false })
  my_fetch(() => setAccount({ id: 1, loaded: true }), 3000)

  const balance = useFetchBalance(account.id, account.loaded)
  console.log(balance)

  return null
}

In essence, I have a state (account) which is updated using a fetch (fake in this case). While the account has not been fetched yet, account.loaded is set to false. This ensures that when useFetchBalance is called it will not fetch the balance. I'd expect the console to log initially a null value, and 6 seconds later (3 for fetching the account and 3 for fetching the balance), to log 42. However, it first prints null 3 times and then prints 42 endlessly in what seems to be batches of two. I am very new to react so any help would be greatly appreciated.

1 answer

  • answered 2019-08-24 00:24 Asaf Aviv

    Your my_fetch inside App component keeps firing on every re-render, call it once when App mounts using useEffect with empty dependencies array, this will make sure my_fetch is going to run only once when the component mounts and not on every re-render

    Don't forget that React functional components are after all functions, so on every re-render the function will get executed, this is why we need to use the helper functions React provide us

    function App() {
      const [account, setAccount] = useState({ id: null, loaded: false })
      const balance = useFetchBalance(account.id, account.loaded)
    
      useEffect(() => {
        my_fetch(() => setAccount({ id: 1, loaded: true }), 3000)
      }, [])
    
      return null
    }