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

React/Redux多页面应用购物车逻辑实现及报错求助

Redux购物车多页面适配问题解决方案

问题概述

之前在单页面应用(所有组件嵌套在App内)中用Redux实现的购物车逻辑正常,但切换到多页面路由应用后出现两个核心问题:

  • Product页面无法将商品添加到购物车
  • Cart页面触发报错:Cannot convert undefined or null to object,错误定位在Object.keys(cart)代码处

错误详情

Cart页面报错信息:

Unexpected Application Error!
Cannot convert undefined or null to object
TypeError: Cannot convert undefined or null to object
at Function.keys ()
at Cart

当前代码

index.jsx

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.jsx';
import VinylClocks from './Components/VinylClocks.jsx';
import CustomClocks from './Components/CustomClocks.jsx';
import About from './Components/About.jsx';
import Contact from './Components/Contact.jsx';
import Cart from './Components/Cart.jsx';
import ProductPage from './Components/ProductPage.jsx';
import './index.css';
import {
  createBrowserRouter,
  RouterProvider,
  Route,
} from "react-router-dom";
import { Provider } from "react-redux";
import store from "./store/store.js";

const router = createBrowserRouter([
  {
    path: "/",
    element: <App/>,
  },
  {
    path: "vinylclocks",
    element: <VinylClocks/>,
  },
  {
    path: "customclocks",
    element: <CustomClocks/>,
  },
  {
    path: "about",
    element: <About/>,
  },
  {
    path: "contact",
    element: <Contact/>,
  },
  {
    path: "cart",
    element: <Cart/>,
  },
  {
    path: "product/productpage/:name",
    element: <ProductPage/>,
  },
]);

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <RouterProvider router={router} />
    <Provider store={store}> 
      <App />
    </Provider>
  </React.StrictMode>,
)

ProductPage.jsx

import React from "react";
import "./ProductPage.css";
import Navbar from "./Navbar.jsx";
import Footer from "./Footer.jsx";
import { data } from "./data.js";
import { useParams } from 'react-router-dom';
import { addItem } from '../features/cart/cartSlice.js';
 

export default function ProductPage() {
  
    const { name } = useParams();

    const item = data.find(item => item.name === name);

    const onClickHandler = (inventoryItem) => {
      dispatch(addItem(inventoryItem));
    };

  if (!item) {
    return <div>No item</div>;
  }
    return (
      <>
        <Navbar/>
        <div className="productPage-container" key={item.id}>
            <div className="image-container">
                <img src={item.img}/>
            </div>
            <div className="about-product">
                <h2 className="product-name">{item.name}</h2>
                <span className="product-type">{item.type}</span>
                <span className="product-price">${item.price}</span>
                <p className="product-desc">{item.desc}</p>
                <button onClick={() => onClickHandler(inventoryItem)} className="addToCart"> Add to Cart</button>
            </div>
        </div>
        <Footer/>
      </>  
      );
}

Cart.jsx

import React from 'react';
import {changeItemQuantity} from '../features/cart/cartSlice.js';

export default function Cart(props) {

  const { cart, dispatch } = props;
  
  const onInputChangeHandler = (name, input) => {
    if (input === '') {
      return;
    }

    const newQuantity = Number(input);

    dispatch(changeItemQuantity(name, newQuantity));

  };

  const cartElements = Object.keys(cart).map(createCartItem);
  const total = calculateTotal(cart);

  return (
    <div id="cart-container">
      <ul id="cart-items">{cartElements}</ul>
      <h3 className="total">
        Total{' '}
        <span className="total-value">
          {total} 
        </span>
      </h3>
    </div>
  );

  function createCartItem(name) {
    const item = cart[name];

    if (item.quantity === 0) {
      return;
    }

    return (
      <li key={name}>
        <p>{name}</p>
        <select
          className="item-quantity"
          value={item.quantity}
          onChange={(e) => {
            onInputChangeHandler(name, e.target.value);
          }}
        >
          {[...Array(100).keys()].map((_, index) => (
            <option key={index} value={index}>
              {index}
            </option>
          ))}
        </select>
      </li>
    );
  }
};

修复步骤

1. 修正Redux Provider的包裹范围

当前代码中RouterProvider和Provider并列渲染,导致路由组件无法访问Redux Store。必须让Provider包裹RouterProvider,确保所有路由组件都能获取到Store。

修改index.jsx的渲染部分:

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <Provider store={store}> 
      <RouterProvider router={router} />
    </Provider>
  </React.StrictMode>,
)

2. 修复ProductPage的添加购物车功能

  • 缺少useDispatch钩子,导致dispatch未定义
  • 点击按钮时传递了未定义的inventoryItem变量

修改后的ProductPage.jsx:

import React from "react";
import "./ProductPage.css";
import Navbar from "./Navbar.jsx";
import Footer from "./Footer.jsx";
import { data } from "./data.js";
import { useParams } from 'react-router-dom';
import { useDispatch } from 'react-redux';
import { addItem } from '../features/cart/cartSlice.js';
 

export default function ProductPage() {
  
    const { name } = useParams();
    const dispatch = useDispatch();

    const item = data.find(item => item.name === name);

    const onClickHandler = (inventoryItem) => {
      dispatch(addItem(inventoryItem));
    };

  if (!item) {
    return <div>No item</div>;
  }
    return (
      <>
        <Navbar/>
        <div className="productPage-container" key={item.id}>
            <div className="image-container">
                <img src={item.img}/>
            </div>
            <div className="about-product">
                <h2 className="product-name">{item.name}</h2>
                <span className="product-type">{item.type}</span>
                <span className="product-price">${item.price}</span>
                <p className="product-desc">{item.desc}</p>
                <button onClick={() => onClickHandler(item)} className="addToCart"> Add to Cart</button>
            </div>
        </div>
        <Footer/>
      </>  
      );
}

3. 修复Cart组件的报错问题

  • 当前通过props接收cart和dispatch,但路由渲染时未传递,导致cart为undefined
  • 改用useSelector和useDispatch钩子直接从Redux Store获取数据
  • 补充未定义的calculateTotal函数
  • 处理cart为空的情况,避免Object.keys报错

修改后的Cart.jsx:

import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import {changeItemQuantity} from '../features/cart/cartSlice.js';

export default function Cart() {
  const cart = useSelector(state => state.cart || {});
  const dispatch = useDispatch();
  
  const onInputChangeHandler = (name, input) => {
    if (input === '') {
      return;
    }

    const newQuantity = Number(input);
    dispatch(changeItemQuantity(name, newQuantity));
  };

  function calculateTotal(cart) {
    return Object.values(cart).reduce((total, item) => {
      return total + (item.price * item.quantity);
    }, 0).toFixed(2);
  }

  const cartElements = Object.keys(cart).map(createCartItem);

  return (
    <div id="cart-container">
      <ul id="cart-items">{cartElements}</ul>
      <h3 className="total">
        Total{' '}
        <span className="total-value">
          ${calculateTotal(cart)} 
        </span>
      </h3>
    </div>
  );

  function createCartItem(name) {
    const item = cart[name];

    if (!item || item.quantity === 0) {
      return null;
    }

    return (
      <li key={name}>
        <p>{name}</p>
        <select
          className="item-quantity"
          value={item.quantity}
          onChange={(e) => {
            onInputChangeHandler(name, e.target.value);
          }}
        >
          {[...Array(100).keys()].map((_, index) => (
            <option key={index} value={index}>
              {index}
            </option>
          ))}
        </select>
      </li>
    );
  }
};

额外说明

  • 确保cartSlice.js的初始状态为一个空对象(如initialState: {}),避免cart为null/undefined
  • calculateTotal函数逻辑需根据实际的cart数据结构调整,确保商品对象包含price和quantity字段

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 20:29:56