1

I am getting a strange behaviour from React useEffect and useState hooks: 7-8 times out of 10 it doesn't set vaults and it keeps having an empty object, while 2-3 times out of ten it does set it.

I am getting some data from the binance testnet blockchain correctly (console logs all log the right data each time, never get undefined) but when I am saving the data into vaults using useState, sometimes it saves it into vaults, sometimes it doesn't.

Somehow, sometimes (very few times) SetVault works and set vaults with the result from the blockchain, sometimes (most of the times) it just doesn't set anything.

Any idea on what is happening?

Code:

 const [vaults, setVaults] = useState({});

 useEffect(() => {
    async function loadBloackchainData() {
      const web3 = new Web3('https://data-seed-prebsc-1-s1.binance.org:8545/');
      const vault = new web3.eth.Contract(VaultABI, testnet.Vaults[0].address);
      const name = await vault.methods.symbol().call();
      console.log('name: ', name);  // ok data is logged correctly
      const totalSupply = parseInt(await vault.methods.totalSupply().call());
      console.log('totalSupply: ', totalSupply); // ok data is logged correctly
      const totalBorrowed = parseInt(await vault.methods.vaultDebtVal().call());
      console.log('totalBorrowed: ', totalBorrowed); // ok data is logged correctly
      const capUtilRate = Number.isNaN(totalSupply / totalBorrowed) ? 0 : totalSupply / totalBorrowed;
      console.log('capUtilRate: ', capUtilRate); // ok data is logged correctly

      // ALL GOOD TILL HERE

      setVaults(vaults => ({
        ...vaults,
        [name]: {
           totalSupply,
           totalBorrowed,
           capUtilRate,
        },
      }));
      console.log('vaults inside useffect: ', vaults); // {}
    }

    loadBloackchainData();
  }, []);
  console.log('vaults outside useffect: ', vaults); // {}
TylerH
  • 20,799
  • 66
  • 75
  • 101
Andrea D_
  • 2,028
  • 3
  • 15
  • 38
  • You doubt is the last console.log working incorrectly? – Appaji Chintimi Jun 30 '21 at 03:37
  • that and also outer console.logs, outside the useEffect also log empty object – Andrea D_ Jun 30 '21 at 03:38
  • This probably isn't the solution but because you are calling this useEffect on mount, you probably can just setVaults to the object rather than getting the previous data and merging that with the new object – Richard Hpa Jun 30 '21 at 03:41
  • That I did because I need to loop through an array and create multiple objects inside vaults. I didn'add the array loop to not add unnecessary complexity – Andrea D_ Jun 30 '21 at 03:45
  • Could you try building the object before hand and then consoling it before setting the state and see if that is working 100% of the time. – Richard Hpa Jun 30 '21 at 03:47
  • Just tried that too. No luck, sorry – Andrea D_ Jun 30 '21 at 03:56
  • Then that is telling me that the issue isn't to do with your setState but all the stuff that is happening before it. – Richard Hpa Jun 30 '21 at 03:58

4 Answers4

2

setVaults has asynchronous logic. I said logic because it doesn't return any Promise. After calling setVaults, It does update state, but when console logging after that, you may not notice it.

Create another useEffect below it to check whether it's being changed.

useEffect(() => {
    console.log('Updated Vaults: ', vaults);
}, [vaults]);
Appaji Chintimi
  • 613
  • 2
  • 7
  • 19
1

Setting up state in React is an async process, which means it's not guaranteed that you will have the updated value available right after you update the state.

Above could be the reason of why you might not get the value in console.log('vaults inside useffect: ', vaults);.

Another thing that might be causing the issue is the same name that you are using in two different place. i.e.

const [vaults, setVaults] = useState({});

&

setVaults(vaults => ({
   ...vaults,
   [name]: {
      totalSupply,
      totalBorrowed,
      capUtilRate,
   },
}));

In above code, you are using the same name vaults, which can make a difference here.

Try changing the code to below and see if it works.

setVaults(prevVaults => ({
   ...prevVaults,
   [name]: {
      totalSupply,
      totalBorrowed,
      capUtilRate,
   },
}));
Milind Agrawal
  • 2,724
  • 1
  • 13
  • 20
1

Setting state variable doesn't run immediately as you are expecting here. The order of execution of the code will be as follows:

const [vaults, setVaults] = useState({});

Initialize state variable vaults as {}.

useEffect(() => {
    ...
  }, []);

The callback inside useEffect is registered, but will not run immediately.

console.log('vaults outside useffect: ', vaults);

This will print {} now because of initial value.

If you return some JSX, it will be rendered now.

After that useEffect callback will be triggered.

async function loadBloackchainData() {
      ...
}
loadBloackchainData();

In loadBloackchainData function call it works as expected until the // ALL GOOD TILL HERE comment.

setVaults(vaults => ({
        ...vaults,
        [name]: {
           totalSupply,
           totalBorrowed,
           capUtilRate,
        },
      }));
console.log('vaults inside useffect: ', vaults); // {}

The setVaults call will not update the value vaults immediately, rather React batches such updates to state and will be run later. So console.log will print the current value of {} for vaults. If you want console.log to have value with which state is updated, maybe run some logic with it, you can try:

setVaults(vaults => {
  const updatedState = ({
        ...vaults,
        [name]: {
           totalSupply,
           totalBorrowed,
           capUtilRate,
        },
  });
  console.log('vaults inside useffect: ', updatedState.vaults);
  return updatedState });
indiansage
  • 71
  • 2
1

setVaults is an async operation, It won't update immediately, To watch the change use useEffect with the dependency of vaults.

const [vaults, setVaults] = useState({});
async function loadBloackchainData() {
  const web3 = new Web3("https://data-seed-prebsc-1-s1.binance.org:8545/");
  const vault = new web3.eth.Contract(VaultABI, testnet.Vaults[0].address);
  const name = await vault.methods.symbol().call();
  console.log("name: ", name); // ok data is logged correctly
  const totalSupply = parseInt(await vault.methods.totalSupply().call());
  console.log("totalSupply: ", totalSupply); // ok data is logged correctly
  const totalBorrowed = parseInt(await vault.methods.vaultDebtVal().call());
  console.log("totalBorrowed: ", totalBorrowed); // ok data is logged correctly
  const capUtilRate = Number.isNaN(totalSupply / totalBorrowed)
    ? 0
    : totalSupply / totalBorrowed;
  console.log("capUtilRate: ", capUtilRate); // ok data is logged correctly

  // ALL GOOD TILL HERE

  setVaults((prev) => ({
    ...prev,
    [name]: {
      totalSupply,
      totalBorrowed,
      capUtilRate,
    },
  }));
  console.log("vaults inside useffect: ", vaults); // {}
}

useEffect(() => {
  loadBloackchainData();
}, []);

useEffect(() => {
  console.log("vaults outside useffect: ", vaults); // First render {}, after setVaults you will get object
}, [vaults]);
Rahul Sharma
  • 9,534
  • 1
  • 15
  • 37