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

React多商品购物车:Add to Cart按钮切换商品后数据异常求助

问题原因分析

你的购物车组件出现异常的核心原因是所有商品共用了同一个items状态对象:

  • 不同商品的选择器(比如第一个和第三个商品的color选择器)修改的是同一个状态下的相同key,后续操作会直接覆盖之前的选择值
  • 所有商品的Add to Cart按钮都打印同一个全局的items状态,导致切换商品操作后,之前的选择数据被覆盖
解决方案:封装独立商品组件

最清晰的解决方式是把单个商品封装成独立组件,让每个组件维护自身的选择状态,实现状态隔离。

修改后的完整代码

import "./Card.css";
import { useState } from "react";

// 独立的商品组件,每个商品维护自己的状态
function ProductItem({ title, description, imageUrl, options }) {
  // 每个商品自己的选择状态
  const [selectedOptions, setSelectedOptions] = useState({});

  const handleChangeCategory = (key, event) => {
    setSelectedOptions(prev => ({ ...prev, [key]: event.target.value }));
  };

  const submitHandler = () => {
    console.log(`${title} 选择数据:`, selectedOptions);
    // 这里可以添加添加到购物车的逻辑,比如调用父组件的回调
    // 清空当前商品的选择(可选)
    setSelectedOptions({});
  };

  return (
    <>
      <div className="container">
        <div className="image-container">
          <img src={imageUrl} alt={title} />
        </div>
        <h2>{title}</h2>
      </div>
      <div className="form-conatiner">
        <div className="selectors">
          <p>{description}</p>
          {/* 根据传入的options渲染选择器 */}
          {options.map(opt => (
            <select
              key={opt.key}
              name={opt.key}
              required
              onChange={(e) => handleChangeCategory(opt.key, e)}
            >
              <option>{opt.label}</option>
              {opt.items.map(item => (
                <option key={item.value} value={item.value}>{item.label}</option>
              ))}
            </select>
          ))}
          <div>
            <button onClick={submitHandler}>Add to Cart</button>
          </div>
        </div>
      </div>
    </>
  );
}

function Card() {
  // 商品列表数据,把每个商品的配置抽离出来,更易维护
  const products = [
    {
      title: "T-Shirt",
      description: "Solid Round Neck T-shirt",
      imageUrl: "https://images.pexels.com/photos/9558601/pexels-photo-9558601.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1",
      options: [
        { key: "color", label: "Color", items: [
          { value: "black", label: "Black" },
          { value: "green", label: "Green" },
          { value: "orange", label: "Orange" }
        ]},
        { key: "quantity", label: "Quantity", items: [
          { value: "1", label: "1" },
          { value: "2", label: "2" },
          { value: "3", label: "3" }
        ]},
        { key: "size", label: "Size", items: [
          { value: "medium", label: "Medium" },
          { value: "large", label: "Large" },
          { value: "small", label: "Small" }
        ]}
      ]
    },
    {
      title: "i-Watch",
      description: "Dizo watch with amlod",
      imageUrl: "https://images.pexels.com/photos/440320/pexels-photo-440320.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1",
      options: [
        { key: "brand", label: "Brand", items: [
          { value: "Apple", label: "Apple" },
          { value: "Samsung", label: "Samsung" },
          { value: "Pixel", label: "Pixel" }
        ]},
        { key: "quantity", label: "Quantity", items: [
          { value: "1", label: "1" },
          { value: "2", label: "2" },
          { value: "3", label: "3" }
        ]},
        { key: "type", label: "Type", items: [
          { value: "29mm", label: "29mm" },
          { value: "34mm", label: "34mm" },
          { value: "42mm", label: "42mm" }
        ]}
      ]
    },
    {
      title: "Hoodie",
      description: "Adidas hoodie with zip",
      imageUrl: "https://images.pexels.com/photos/1661471/pexels-photo-1661471.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1",
      options: [
        { key: "color", label: "Color", items: [
          { value: "gray", label: "Gray" },
          { value: "white", label: "White" },
          { value: "cyan", label: "Cyan" }
        ]},
        { key: "quantity", label: "Quantity", items: [
          { value: "1", label: "1" },
          { value: "2", label: "2" },
          { value: "3", label: "3" }
        ]},
        { key: "size", label: "Size", items: [
          { value: "39(S)", label: "39(S)" },
          { value: "42(M)", label: "42(M)" },
          { value: "46(L)", label: "46(L)" }
        ]}
      ]
    }
  ];

  return (
    <div className="main-container">
      {/* 遍历商品列表渲染每个商品组件 */}
      {products.map((product, index) => (
        <ProductItem key={index} {...product} />
      ))}
    </div>
  );
}

export default Card;

关键改进点

  1. 状态隔离:每个ProductItem组件维护自己的selectedOptions状态,不同商品的选择操作互不影响
  2. 组件复用:抽离商品配置数据,通过遍历渲染商品,后续新增商品只需添加配置即可,无需重复写组件代码
  3. 逻辑清晰:每个组件只负责自身的选择逻辑和提交逻辑,代码更易维护和调试

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 02:30:56