React 状态无法使用 UseEffect 更新

React State Fails To Update with UseEffect

尝试通过 React 状态管理更新数组时,状态数组已填充,但用户界面无法更新。用户界面仅在我单击导航栏并重新路由到当前页面后更新(在这种情况下 useEffect 不会再次 运行,但 UI 是已更新)。

州代码

 const[isFetched, setIsFetched] = useState(false);
  const[balances, setBalances] = useState<IBalance[]>([]);
  
  const[num, setNum] = useState(0);

  useEffect(() => {
    console.log(balances);
    // LOGS A POPULATED ARRAY
    console.log(balances.length);
    // LOGS 0
  }, [balances]);
  
  useEffect(() => {
      const fetchBalances = async() =>{
        let bals:IBalance[] = await kryptikService.getBalanceAllNetworks(kryptikWallet);
        console.log("RECIEVED BALANCES:");
        console.log(bals);
        console.log(bals.length);
        setBalances(bals);
        setIsFetched(true);
      }
      fetchBalances();
    }, []);

UI代码

      <h2>Your Balances</h2>
      <Divider/>
      {
        !isFetched?<p>Loading Balances.</p>:
        <ul role="list" className="divide-y divide-gray-200 dark:divide-gray-700">
          {balances.map((balance:IBalance) => (
              <ListItem title={balance.fullName} imgSrc={balance.iconPath} subtitle={balance.ticker} amount={balance.amountCrypto}/>
          ))}
        </ul>
      }
    </div>

获取处理程序(在 UseEffect 中调用)

    getBalanceAllNetworks = async(walletUser:IWallet):Promise<IBalance[]> =>{
    let networksFromDb = this.getSupportedNetworkDbs();
    // initialize return array
    let balances:IBalance[] = [];
    networksFromDb.forEach(async nw => {
        let network:Network = new Network(nw.fullName, nw.ticker);
        let kryptikProvider:KryptikProvider = await this.getKryptikProviderForNetworkDb(nw);
        if(network.getNetworkfamily()==NetworkFamily.EVM){
            if(!kryptikProvider.ethProvider) throw Error(`No ethereum provider set up for ${network.fullName}.`);
            let ethNetworkProvider:JsonRpcProvider = kryptikProvider.ethProvider;
            console.log("Processing Network:")
            console.log(nw.fullName);
            // gets all addresses for network
            let allAddys:string[] = await walletUser.seedLoop.getAddresses(network);
            // gets first address for network
            let firstAddy:string = allAddys[0];
            console.log(`${nw.fullName} Addy:`);
            console.log(firstAddy);
            console.log(`Getting balance for ${nw.fullName}...`);
            // get provider for network
            let networkBalance = await ethNetworkProvider.getBalance(firstAddy);
            console.log(`${nw.fullName} Balance:`);
            console.log(networkBalance);
            // prettify ether balance
            let networkBalanceAdjusted:Number = BigNumber.from(networkBalance)
            .div(BigNumber.from("10000000000000000"))
            .toNumber() / 100;
            let networkBalanceString = networkBalanceAdjusted.toString();
            let newBalanceObj:IBalance = {fullName:nw.fullName, ticker:nw.ticker, iconPath:nw.iconPath, 
                amountCrypto:networkBalanceString}
            // add adjusted balance to balances return object
            balances.push(newBalanceObj);
        }
    });
    return balances;
}

注意:数组是不同的引用,因此浅层相等检查应该没有问题。此外,更新后的 balances 数组包含对象,但长度记录为零,如第一个代码片段所示。任何帮助将不胜感激!

您正在改变余额而不是更新余额。

将 balances.push 更改为 setBalances(prevState => [...prevState, newBalance])

问题

问题是您正在使用异步回调在 forEach 循环中迭代 networksFromDb 数组。异步回调不是问题,它是 Array.protptype.forEach 同步的, getBalanceAllNetworks 回调可以'不要等待循环回调解决。它 returns 在填充数组之前将空 balances 数组发送给调用者。

数组 仍然 但是,单击 link 足以触发 React 重新渲染并公开突变的 balances 状态数组。

解决方案

不使用 .forEach 循环进行异步回调,而是将 networksFromDb 映射到 Promise 数组并使用 Promise.all 并等待它们全部解决,然后返回填充的 balances数组.

示例:

const getBalanceAllNetworks = async (
  walletUser: IWallet
): Promise<IBalance[]> => {
  const networksFromDb = this.getSupportedNetworkDbs();

  const asyncCallbacks = networksFromDb
    .filter((nw) => {
      const network: Network = new Network(nw.fullName, nw.ticker);
      return network.getNetworkfamily() == NetworkFamily.EVM;
    })
    .map(async (nw) => {
      const kryptikProvider: KryptikProvider = await this.getKryptikProviderForNetworkDb(
        nw
      );

      if (!kryptikProvider.ethProvider) {
        throw Error(`No ethereum provider set up for ${network.fullName}.`);
      }

      const ethNetworkProvider: JsonRpcProvider = kryptikProvider.ethProvider;

      // gets all addresses for network
      const allAddys: string[] = await walletUser.seedLoop.getAddresses(
        network
      );

      // gets first address for network
      const firstAddy: string = allAddys[0];

      // get provider for network
      const networkBalance = await ethNetworkProvider.getBalance(firstAddy);

      // prettify ether balance
      const networkBalanceAdjusted: Number =
        BigNumber.from(networkBalance)
          .div(BigNumber.from("10000000000000000"))
          .toNumber() / 100;
      const networkBalanceString = networkBalanceAdjusted.toString();

      const newBalanceObj: IBalance = {
        fullName: nw.fullName,
        ticker: nw.ticker,
        iconPath: nw.iconPath,
        amountCrypto: networkBalanceString
      };
      // add adjusted balance to balances return object
      return newBalanceObj;
    });

  const balances: IBalance[] = await Promise.all(asyncCallbacks);
  return balances;
};