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
相关产品推荐
相关产品推荐

