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

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状态,但第一次调用的状态更新还未完成,第二次调用就读取了旧的状态值,导致前一次的更新被覆盖,最终只有最后一个元素的计算值生效。

此外还有两个次要问题:

  1. CartInner使用defaultValue属于非受控组件写法,cart状态更新时输入框无法同步更新;
  2. 用数组索引作为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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 02:30:59