Состояние React не обновляется с помощью UseEffect

При попытке обновить массив с помощью управления состоянием React массив состояний заполняется, но пользовательский интерфейс не обновляется. Пользовательский интерфейс обновляется только после того, как я нажимаю на панель навигации и перенаправляюсь на текущую страницу (в этом случае использованиеЭффект не запускается снова, но пользовательский интерфейс обновляется).

Код штата

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

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

Код пользовательского интерфейса

      <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.info("Processing Network:")
            console.info(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.info(`${nw.fullName} Addy:`);
            console.info(firstAddy);
            console.info(`Getting balance for ${nw.fullName}...`);
            // get provider for network
            let networkBalance = await ethNetworkProvider.getBalance(firstAddy);
            console.info(`${nw.fullName} Balance:`);
            console.info(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 содержит объекты, но длина регистрируется как нулевая, как показано в первом фрагменте кода. Любая помощь будет высоко оценена!

Используйте for..of для обработки асинхронного кода в цикле, а не forEach

morganney 13.05.2022 22:08

Все, что вам нужно сделать, это прочитать документы на forEach: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…. Он выделен заметкой на синем фоне.

morganney 13.05.2022 22:22
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Навигация по приложениям React: Исчерпывающее руководство по React Router
Навигация по приложениям React: Исчерпывающее руководство по React Router
React Router стала незаменимой библиотекой для создания одностраничных приложений с навигацией в React. В этой статье блога мы подробно рассмотрим...
Массив зависимостей в React
Массив зависимостей в React
Все о массиве Dependency и его связи с useEffect.
2
2
31
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вы изменяете балансы вместо того, чтобы обновлять их.

Измените balances.push на setBalances(prevState => [...prevState, newBalance])

Ответ принят как подходящий

Проблема

Проблема в том, что вы повторяете массив networksFromDb в цикле forEach с асинхронным обратным вызовом. Проблема не в асинхронном обратном вызове, дело в том, что Array.protptype.forEachявляется синхронный, а обратный вызов getBalanceAllNetworks не может дождаться разрешения обратных вызовов цикла. Он возвращает пустой массив balances вызывающей стороне до заполнения массива.

Однако массив все еще заполняется, и щелчка по ссылке достаточно, чтобы вызвать повторную визуализацию React и открыть измененный массив состояний balances.

Решение

Вместо использования цикла .forEach для асинхронного обратного вызова сопоставьте networksFromDb с массивом промисов и используйте 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;
};

Привет, спасибо за обстоятельный ответ! Вы меня на правильном пути. В итоге я использовал простой цикл for, и все проблемы были решены.

jettblu 16.05.2022 05:33

Другие вопросы по теме