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

React购物车组件报错:无法读取undefined的productName属性

问题描述

我开发了一个CartSummary组件,用于展示已添加到购物车的商品。当用户触发addProduct方法时,会重新从服务器获取购物车数据并更新状态(购物车信息存储在数据库中,增删商品时会重新加载购物车)。但出现了如下错误:

Uncaught TypeError: Cannot read properties of undefined (reading 'productName')

我推测该错误由异步编程导致,尝试在购物车数据到达客户端前使用加载器,但未生效;添加新商品后状态也未重新加载。通过控制台打印cart信息(在CartSummary组件中标记位置)发现,cartItems初始为空,之后才会填充,因此我添加了cart.cartItems.length>0的判断,但问题仍未解决。


CartSummary组件(用于向用户展示购物车详情)

import React, {useEffect} from 'react';
import {Badge, DropdownItem, DropdownMenu, DropdownToggle, Spinner, UncontrolledDropdown} from "reactstrap";
import {useDispatch, useSelector} from "react-redux";
import {cartActions} from "../redux/slice/cartSlice";
import {useNavigate} from "react-router-dom"
import alertify from "alertifyjs";
import {fetchCart} from "../redux/thunk/cartActionThunk";
import {PageSpinner} from "./spinner/Spinner";

const CartSummary = () => {

    const cart = useSelector(state => (state.cartStore.cart))
    const isLoading = useSelector(state => (state.uiStore.isLoading))
    const dispatch = useDispatch()
    const handleRemoveFromCart = (product) => {
        dispatch(cartActions.removeFromCart(product.id))
        alertify.error(product.productName + " removed from the cart", 1)
    }
    const navigate = useNavigate()

    useEffect(() => {
        // 组件挂载时获取购物车数据
        dispatch(fetchCart())
    }, [dispatch])

    console.log(cart) // HERE I LOG THE CART 
    return (
        <div>
            <UncontrolledDropdown nav inNavbar>
                <DropdownToggle nav caret>
                    Cart
                </DropdownToggle>
                <DropdownMenu>
                    {
                        isLoading ?
                            <PageSpinner></PageSpinner>
                            :
                            cart.cartItems.map((item) => (
                                <DropdownItem
                                    style={{
                                        justifyContent: "space-between",
                                        display: "flex",
                                        alignContent: "center",
                                        margin: 10
                                    }}
                                    key={item.id}>
                                    {/* 添加可选链保护,避免product为undefined时报错 */}
                                    {item.product?.productName}
                                    <Badge color="success">{item.totalQuantity}</Badge>
                                </DropdownItem>
                            ))
                    }
                    <DropdownItem divider/>
                    <DropdownItem style={{textAlign: "end"}}> 
                        <Badge color="success">${cart.totalPrice}</Badge>
                    </DropdownItem>
                    <DropdownItem style={{textAlign: "center"}} onClick={() => navigate("/cart-details")}>
                        Show cart details
                    </DropdownItem>
                </DropdownMenu>
            </UncontrolledDropdown>
        </div>
    );
};

export default CartSummary;

Cart Actions Thunk

import {API_URL} from "../../const/url";
import {cartActions} from "../slice/cartSlice";
import alertify from "alertifyjs";
import {uiActions} from "../slice/uiSlice";

export const addToCart = (product) => {
    const productDto = {
        "id": product.id,
        "productName": product.productName,
        "quantityPerUnit": product.quantityPerUnit,
        "unitPrice": product.unitPrice,
        "unitsInStock": product.unitsInStock,
        "categoryId": product.category.id
    }

    const userId = 1;
    return (dispatch) => {
        dispatch(uiActions.setIsLoading(true))

        fetch(API_URL + '/cartItem?userId=' + userId, {
                method: 'POST',
                headers: {"Content-Type": "application/json"},
                body: JSON.stringify(productDto)
            }
        ).then((response) => {
            if (!response.ok) {
                throw Error("Couldn't add " + productDto.productName + " to the cart.")
            }
            return response.json()
        }).then((result) => {
            // 添加成功后重新获取完整购物车数据,而非手动添加
            dispatch(fetchCart())
            dispatch(uiActions.setIsLoading(false))
            alertify.success(productDto.productName + " added successfully.")
        }).catch((err) => {
            dispatch(uiActions.setError(err))
            dispatch(uiActions.setIsLoading(false))
            alertify.error(err.message)
        })
    }
}

export const fetchCart = () => {
    const userId = 1;
    return (dispatch)=>{
        dispatch(uiActions.setIsLoading(true))

        fetch(API_URL + '/cartItem?userId=' + userId, {
            method: 'GET'
        }).then((response)=>{
            if(!response.ok){
                throw Error("Couldn't fetch cart data")
            }
            return response.json()
        }).then((result)=>{
            dispatch(cartActions.fetchCart(result))
            dispatch(uiActions.setIsLoading(false))
        }).catch((err)=>{
            dispatch(uiActions.setError(err))
            dispatch(uiActions.setIsLoading(false))
            alertify.error(err.message)
        })
    }
}

export const removeFromCart = (product) => {
    return (dispatch) => {
        fetch(API_URL + '/')
    }
}

CartSlice

import { createSlice } from '@reduxjs/toolkit';

const cartSlice = createSlice({
    name: 'cart',
    initialState: {cart: {cartItems: [], totalPrice: 0, totalQuantity: 0}},
    reducers: {
        //payload是商品数据
        addProduct(state, action) {
            // 修正:使用action.payload而非action本身
            state.cart.cartItems.push(action.payload)
        },
        //payload是商品id
        removeFromCart(state, action) {
            // 修正:字段名从cartItemList改为cartItems
            const existingCartItem = state.cart.cartItems.find((cartItem) => cartItem.product.id === action.payload)
            if(existingCartItem) {
                state.cart.totalPrice -= existingCartItem.totalPrice;
                state.cart.totalQuantity -= existingCartItem.totalQuantity;
                state.cart.cartItems = state.cart.cartItems.filter((cartItem) => cartItem.product.id !== action.payload)
            }
        },
        fetchCart(state, action) {
            state.cart = action.payload
        }
    }
})

export const cartActions = cartSlice.actions;
export default cartSlice;

购物车JSON结构

{
    "id": 1,
    "cartItems": [
        {
            "id": 19,
            "totalQuantity": 1,
            "totalPrice": 12.5,
            "product": {
                "id": 3,
                "productName": "Scottish Longbreads",
                "quantityPerUnit": "10 boxes x 8 pieces",
                "unitPrice": 12.5,
                "unitsInStock": 6,
                "categoryId": 3
            }
        },
        {
            "id": 20,
            "totalQuantity": 1,
            "totalPrice": 31.0,
            "product": {
                "id": 10,
                "productName": "Ikura",
                "quantityPerUnit": "12 - 200 ml jars",
                "unitPrice": 31.0,
                "unitsInStock": 31,
                "categoryId": 8
            }
        },
        {
            "id": 21,
            "totalQuantity": 2,
            "totalPrice": 69.6,
            "product": {
                "id": 4,
                "productName": "Mozzarella di Giovanni",
                "quantityPerUnit": "24 - 200 g pkgs.",
                "unitPrice": 34.8,
                "unitsInStock": 14,
                "categoryId": 4
            }
        },
        {
            "id": 22,
            "totalQuantity": 1,
            "totalPrice": 37.0,
            "product": {
                "id": 6,
                "productName": "Queso Manchego La Pastora",
                "quantityPerUnit": "10 - 500 g pkgs.",
                "unitPrice": 37.0,
                "unitsInStock": 86,
                "categoryId": 6
            }
        },
        {
            "id": 23,
            "totalQuantity": 1,
            "totalPrice": 38.0,
            "product": {
                "id": 8,
                "productName": "Queso Manchego La Pastora",
                "quantityPerUnit": "10 - 500 g pkgs.",
                "unitPrice": 38.0,
                "unitsInStock": 86,
                "categoryId": 8
            }
        },
        {
            "id": 24,
            "totalQuantity": 1,
            "totalPrice": 33.25,
            "product": {
                "id": 5,
                "productName": "Wimmers gute Semmelknödel",
                "quantityPerUnit": "20 bags x 4 pieces",
                "unitPrice": 33.25,
                "unitsInStock": 22,
                "categoryId": 5
            }
        },
        {
            "id": 25,
            "totalQuantity": 1,
            "totalPrice": 13.0,
            "product": {
                "id": 2,
                "productName": "Original Frankfurer grüne So",
                "quantityPerUnit": "12 boxes",
                "unitPrice": 13.0,
                "unitsInStock": 11,
                "categoryId": 8
            }
        }
    ],
    "totalQuantity": 7,
    "totalPrice": 234.35
}

问题根源与修复总结

  1. Reducer参数错误:addProduct中误将action推入数组,实际应使用action.payload,导致数组中混入非商品数据,访问product时出现undefined。
  2. 未初始化获取数据:CartSummary组件挂载时未调用fetchCart,初始无购物车数据,渲染时触发错误。
  3. 字段名不匹配:removeFromCart中使用了不存在的cartItemList字段,应改为cartItems。
  4. 添加商品后状态未同步:addToCart成功后仅手动添加商品,未重新获取服务器端完整购物车数据,导致totalPrice等计算字段未更新。
  5. 缺乏空值保护:渲染时未对item.product做存在性判断,添加可选链item.product?.productName避免报错。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 15:55:25