React购物车组件中数组首个元素未更新问题排查
问题描述
我正在构建一个购物车系统,用户可修改每项资产的投资金额,查看该金额按10%年利率复利5年后的价值。当前遇到问题:仅数组第二个元素的计算值能更新,首个元素无法正确更新。例如输入第一个资产金额为100、第二个为100000时,projectedGrowth数组返回[6442.04, 16105.1],第二个结果正确但第一个错误。
相关代码
Checkout组件
import React, { useEffect, useState } from "react"; import CartInner from "./CartInner"; export default function Checkout() { const [cart, setCart] = useState<any>([ { price: 2000, id: 1 }, { price: 4000, id: 2 } ]); const [projectedGrowth, setProjectedGrowth] = useState<any>([]); const handleOnChange = (index: any, e: any) => { const newValue = { price: Number(e.target.value), id: index + 1, property_id: index + 1 }; const newArray = [...cart]; newArray.splice(index, 1, newValue); setCart(newArray); }; const getGrowthAmount = (index: any) => { console.log(cart[index].price, "default values for price"); const newValue = calcTotalCompoundInterest(cart[index].price, 5, 10); const newArray = [...projectedGrowth]; newArray.splice(index, 1, newValue); setProjectedGrowth(newArray); }; // whenever the cart is updated call the getGrowthAmount function useEffect(() => { for (let index = 0; index < cart.length; index++) { getGrowthAmount(index); } }, [cart]); console.log(projectedGrowth, "projectedGrowth"); console.log(cart, "cart"); return ( <> <form className="grid gap-8 grid-cols-3"> <div className="col-span-2"> {cart.map((element, index) => { return ( <fieldset key={index}> <CartInner // projectedGrowth={projectedGrowth[index]} element={element} handleOnChange={(e) => handleOnChange(index, e)} defaultValues={cart[index]?.price} /> </fieldset> ); })} </div> <button>Proceed to payment</button> </form> </> ); }
复利计算函数
function calcTotalCompoundInterest(total: any, year: any, rate: any) { var interest = rate / 100 + 1; return parseFloat((total * Math.pow(interest, year)).toFixed(4)); }
CartInner子组件
import PureInput from "./PureInput"; import React, { useEffect, useState, useMemo } from "react"; const CartInner = React.forwardRef( ( { handleOnChange, price, projectedGrowth, defaultValues, id, ...inputProps }: any, ref: any ) => { return ( <input min={200} max={price} handleOnChange={handleOnChange} type="number" step={200} defaultValue={defaultValues} id={id} ref={ref} {...inputProps} /> ); } ); export default CartInner;
错误原因
核心问题是React状态更新的异步性:
当useEffect循环调用getGrowthAmount时,每次调用setProjectedGrowth都依赖当前的projectedGrowth状态,但第一次调用的状态更新还未完成,第二次调用就读取了旧的状态值,导致前一次的更新被覆盖,最终只有最后一个元素的计算值生效。
此外还有两个次要问题:
CartInner使用defaultValue属于非受控组件写法,cart状态更新时输入框无法同步更新;- 用数组索引作为
key,元素顺序变化时会引发React渲染异常。
解决方案
1. 批量计算并更新状态(推荐)
放弃循环单独更新,一次性计算所有资产的复利值后批量设置状态,彻底避免异步状态依赖问题:
// 移除原getGrowthAmount函数,修改useEffect useEffect(() => { const newGrowthArray = cart.map(item => calcTotalCompoundInterest(item.price, 5, 10) ); setProjectedGrowth(newGrowthArray); }, [cart]);
2. 修复受控组件问题
将CartInner改为受控组件,确保输入值与状态同步:
// 修改CartInner组件 const CartInner = React.forwardRef( ( { onChange, value, price, id, ...inputProps }: any, ref: any ) => { return ( <input min={200} max={price} onChange={onChange} type="number" step={200} value={value} id={id} ref={ref} {...inputProps} /> ); } ); // Checkout组件中调用CartInner时传递正确属性 <CartInner onChange={(e) => handleOnChange(index, e)} value={element.price} price={element.price} />
3. 优化列表项key值
使用元素唯一id作为key,保证渲染稳定性:
{cart.map((element, index) => { return ( <fieldset key={element.id}> <CartInner onChange={(e) => handleOnChange(index, e)} value={element.price} price={element.price} /> </fieldset> ); })}
内容的提问来源于stack exchange,提问作者a7dc
相关产品推荐
相关产品推荐

