Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

react hooks (useEffect) infinite loop clarification

This question is related to this previous question of mine. I have written two custom hooks. The first is useFetchAccounts whose job is to fetch some data about user accounts and looks like this:

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

function useFetchAccounts() {

  const [data, setData] = useState({accounts: [], loaded: false})

  useEffect(() => {
    my_fetch(() => {setData({accounts: [{id:1},{id:2}], loaded: true})}, 3000)
  }, [])

  return data
}

Nothing special about this hook, simply fetches the ids of the accounts.

Now I have another hook, useFetchBalancesWithIDs(account_ids, accounts_loaded), which is meant to take the ids from the previous step and fetch the balances of these accounts if the accounts have been loaded. It looks like this:

function useFetchBalanceWithIDs(account_ids, accounts_loaded) {

  const [balances, setBalances] = useState(null)

  useEffect(() => {
    if (!accounts_loaded) return; // only fetch if accounts are loaded
    my_fetch(() => {
      setBalances(account_ids.map(id => 42+id))
    }, 3000)
  }, [account_ids, accounts_loaded])

  return balances
}

As you can see if the accounts_loaded is false, it will not perform the fetch. Together I am using them as follows:

function App() {

  const account_data = useFetchAccounts()

  const accounts = account_data.accounts
  const account_ids = accounts.map(account => account.id) // extract ids

  const balance = useFetchBalanceWithIDs(account_ids, account_data.loaded)

  console.log(balance)

  return null
}

Unfortunately, this results in an infinite loop. What does work is changing the useFetchBalancesWithIDs to this:

function useFetchBalanceWithAccounts(accounts, accounts_loaded) {

  const [balances, setBalances] = useState(null)

  useEffect(() => {
    if (!accounts_loaded) return; // only fetch if accounts are loaded
    my_fetch(() => {
      setBalances(accounts.map(account => 42+account.id))
    }, 3000)
  }, [accounts, accounts_loaded])

  return balances
}

which performs the ids extraction within. I'm using it like this:

function App() {

  const account_data = useFetchAccounts()

  const accounts = account_data.accounts

  const balance = useFetchBalanceWithAccounts(accounts, account_data.loaded)

  console.log(balance)

  return null
}

which runs just fine. So the problem seems to be related to the extraction of the IDs from within the App component, which seems to be triggering the useFetchBalanceWithIDs all the time, even though the account_ids do not change value. Can someone help explain this behaviour please? It's OK for me to use useFetchBalanceWithAccounts but I would like to understand why useFetchBalanceWithIDs doens't work. Thank you!

like image 282
linuxfever Avatar asked Dec 14 '25 13:12

linuxfever


1 Answers

The issue is that you're using the map function to get an array of your account ids. React uses Object.is to determine if your values in the dependency array has changed in each render cycle. The map function returns a new array, so even though the values in your array are the same, the comparison check evaluates to false, and the effect is run.

like image 140
Stephan Olsen Avatar answered Dec 16 '25 19:12

Stephan Olsen



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!