You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Next.js中State Setter无法完整设置状态的问题排查

状态设置与数组渲染异常问题

尝试设置数组类型状态listDocuments时,仅能显示一个元素。打印retList能看到所有内容,但调用setListDocuments后状态未正常生效。怀疑和异步函数有关,这是该代码与项目中同类正常函数的唯一区别,所有状态设置和数据获取都在useEffect中执行。

核心问题原因

  • 异步循环的闭包陷阱:在avgMap.forEach(async (value, key) => {})异步循环里,每次调用setListDocuments([...listDocuments, insertAvg])时,listDocuments捕获的都是初始空数组状态。异步操作延迟执行,当更新触发时组件未重新渲染,导致每次都基于空数组添加单个元素,最终只保留最后一次添加的结果。
  • forEach不支持异步顺序执行:snapshot.forEach和avgMap.forEach中的异步函数不会等待前一个执行完毕就启动下一个,导致avgMap构建、retList填充顺序错乱,状态更新时机混乱。
  • useEffect依赖项错误:将listDocuments和documents加入依赖数组,会触发重复执行useEffect,但dataFetchedRef的限制会让后续执行直接返回,干扰初始化流程。

修复方案

1. 用for...of替代forEach处理异步循环

forEach无法正确等待异步操作完成,改用for...of确保顺序执行:

// 替换原snapshot.forEach的逻辑
for (const doc of snapshot.docs) {
  const obj = doc.data()
  data.push(obj)
  let tic = doc.data().stockTicker;
  
  if (avgMap.has(tic)) {
    console.log("Has")
    let existingData = avgMap.get(tic)
    const type = doc.data().type;
    let newShares = parseFloat(existingData.shares);
    let newPrice = parseFloat(existingData.price);
    const oldAmount = newShares * newPrice
    if (type === "buy") {
      newShares = newShares + parseFloat(doc.data().shares);
      newPrice = (oldAmount + parseFloat(doc.data().price))/newShares;
    } else {
      newShares = newShares - parseFloat(doc.data().shares);
      newPrice = (oldAmount - parseFloat(doc.data().price))/newShares;
    }

    const newData = {
      price: newPrice,
      shares: newShares,
      stockTicker: tic,
    }
    avgMap.set(tic, newData)
  } else {
    console.log("Doesnt Have")
    avgMap.set(tic, doc.data())
  }
}

2. 批量更新状态,避免闭包问题

先把所有数据收集到retList,再一次性更新状态,不要在循环内逐个调用setListDocuments:

let retList = []
// 替换原avgMap.forEach的逻辑
for (const [key, value] of avgMap.entries()) {
  const newPrice = await getStockPrice(key)
  const currentPrice = parseFloat(newPrice.toFixed(2))
  const pl = (parseFloat(value.shares)*(currentPrice - parseFloat(value.price))).toFixed(2)
  const fixedPrice = (value.price).toFixed(2)
  let insertAvg = {
    ticker: key,
    shares: value.shares,
    averagePrice: fixedPrice,
    currentPrice,
    profitLoss: pl,
    dividendYield: "Coming Soon"
  }
  retList.push(insertAvg)
}
// 批量更新状态
setListDocuments(retList)

3. 修正useEffect依赖数组

只保留必要的依赖,避免不必要的重复执行:

useEffect(() => {
  if (!currentUser) {
    router.push('/login');
  } else {
    if(dataFetchedRef.current) return;
    dataFetchedRef.current = true;
    
    const setData = async() => {
      // ... 内部逻辑不变 ...
    }
    setData()
  }
}, [currentUser, router]) // 仅保留触发初始化的必要依赖

4. 给列表项添加唯一key

和documents.map的正确写法一致,为listDocuments.map的元素添加唯一key,避免渲染异常:

{listDocuments?.map(doc => {
  return (
    <tr key={doc.ticker} className="bg-gray-600 flex flex-col flex-nowrap sm:table-row rounded-lg sm:rounded-none mb-2 sm:mb-0">
      <th className="p-3 text-left">Type</th>
      <th className="p-3 text-left">Shares</th>
      <th className="p-3 text-left">Price</th>
      <th className="p-3 text-left">Return</th>
      <th className="p-3 text-left" width="110px">Actions</th>
    </tr>
  )
})}

内容的提问来源于stack exchange,提问作者Dotman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 16:32:21