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;
};
尝试通过 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;
};