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

React购物车如何将countInStock存入localStorage实现状态持久化

问题背景

开发购物车应用时存在如下问题:商品库存因用户加购耗尽后,加购按钮会正常置为禁用状态,但页面刷新后按钮会恢复为可点击状态,需要将product.countInStock字段持久化保存到localStorage,保证刷新后按钮状态正确。

原有代码
import { Button } from 'react-bootstrap';
import { Card } from 'react-bootstrap';
import { Link } from 'react-router-dom';
import React, { useContext } from 'react';
import Rating from './Rating';
import axios from 'axios';
import { Store } from '../Store';

function Product(props){

    const {product} = props;

    const {state , dispatch:ctxDispatch} = useContext(Store);
    const {cart: {cartItems}} = state

    const addToCartHandler = async (item )=>{
      const existItem = cartItems.find((x)=> x._id === product._id);
       const quantity = existItem ? existItem.quantity+1:1 ; 

      const {data} = await axios.get(`/api/products/${item._id}`);
      if(data.countInStock < quantity){
          window.alert('sorry product is out of stock')
          return;
      }
  product.countInStock--
       ctxDispatch({
           type:'CART_ADD_ITEM' 
           , payload:{...item , quantity},
       });
      };
      

    return(

        <Card>


        <Link to={`/product/${product.slug}`}> 
          <img src={product.image} className="card-img-top" alt={product.name} />
        </Link>
        <Card.Body>
        <Link to={`/product/${product.slug}`}>
            <Card.Title>{product.name}</Card.Title>
        </Link>
        <Rating rating={product.rating} numReviews={product.numReviews} />
        <Card.Text>${product.price}</Card.Text>

        {  product.countInStock === 0 ? (

          
          <Button  color="light" disabled={true} >  Out of stock</Button>
          
        ):(
          
          <Button onClick={() => addToCartHandler(product)}>Add to cart</Button>
        )}
      </Card.Body>
    </Card>
    )}
实现方案

核心逻辑

  • 封装localStorage读写方法,统一管理本地存储的库存数据,避免零散操作
  • 组件初始化时,优先读取localStorage中存储的对应商品库存,没有对应记录时才使用props传入的原始库存值
  • 每次加购成功扣减库存后,同步将最新库存写入localStorage
  • 修正原代码直接修改props值的问题(React中props为只读,直接修改会触发不可预期的渲染问题),用组件本地状态维护当前展示的库存值

完整修改后代码

import { Button } from 'react-bootstrap';
import { Card } from 'react-bootstrap';
import { Link } from 'react-router-dom';
import React, { useContext, useState, useEffect } from 'react';
import Rating from './Rating';
import axios from 'axios';
import { Store } from '../Store';

// 本地存储key,单独加前缀避免和其他存储字段冲突
const STOCK_STORAGE_KEY = 'shop_product_stock_map';

// 读取本地存储的所有库存映射
const getLocalStock = () => {
  const stockStr = localStorage.getItem(STOCK_STORAGE_KEY);
  return stockStr ? JSON.parse(stockStr) : {};
};

// 更新单个商品的库存到本地存储
const saveStockToLocal = (productId, countInStock) => {
  const stockMap = getLocalStock();
  stockMap[productId] = countInStock;
  localStorage.setItem(STOCK_STORAGE_KEY, JSON.stringify(stockMap));
};

function Product(props){
    const {product} = props;
    const {state , dispatch:ctxDispatch} = useContext(Store);
    const {cart: {cartItems}} = state;

    // 初始化库存:优先读本地存储,没有则用props传入的原始库存
    const [currentStock, setCurrentStock] = useState(() => {
      const localStockMap = getLocalStock();
      return localStockMap[product._id] ?? product.countInStock;
    });

    // 每次库存更新时同步写入本地存储
    useEffect(() => {
      saveStockToLocal(product._id, currentStock);
    }, [currentStock, product._id]);

    const addToCartHandler = async (item)=>{
      const existItem = cartItems.find((x)=> x._id === product._id);
      const quantity = existItem ? existItem.quantity+1 : 1; 

      const {data} = await axios.get(`/api/products/${item._id}`);
      // 以接口返回的最新服务端库存为准做校验,避免超卖
      if(data.countInStock < quantity){
          window.alert('sorry product is out of stock');
          // 如果服务端库存比本地记录的小,同步更新本地库存
          setCurrentStock(data.countInStock);
          return;
      }
      // 扣减后更新本地库存状态,会自动触发useEffect同步到localStorage
      setCurrentStock(prev => prev - 1);

      ctxDispatch({
           type:'CART_ADD_ITEM',
           payload:{...item , quantity},
      });
    };
      
    return(
        <Card>
          <Link to={`/product/${product.slug}`}> 
            <img src={product.image} className="card-img-top" alt={product.name} />
          </Link>
          <Card.Body>
            <Link to={`/product/${product.slug}`}>
                <Card.Title>{product.name}</Card.Title>
            </Link>
            <Rating rating={product.rating} numReviews={product.numReviews} />
            <Card.Text>${product.price}</Card.Text>

            {currentStock === 0 ? (
              <Button color="light" disabled={true}>Out of stock</Button>
            ):(
              <Button onClick={() => addToCartHandler(product)}>Add to cart</Button>
            )}
          </Card.Body>
        </Card>
    )
}

export default Product;

补充说明

  • 上述实现是前端侧的临时持久化方案,真实电商场景下库存校验必须以服务端返回结果为准,前端本地存储仅用于优化刷新后的展示状态,避免用户体验断层
  • 如果项目中已经全局持久化了cartItems数据,也可以直接用商品原始库存 - 购物车中该商品已加购数量计算剩余库存,不需要单独存储countInStock字段,逻辑会更简洁

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 13:12:30