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

React useReducer触发一次却四次添加订单状态问题排查与修复

咖啡配送应用订单历史模块问题:一次性添加四条相同订单

我正在开发一款包含购物车、支付表单和订单历史的咖啡配送应用,当前遇到订单历史模块的问题:完成订单时,本应将购物车商品与表单信息整合后添加至订单历史,但触发添加订单数组的函数时,代码会一次性创建四条完全相同的订单。

相关代码如下:

Reducer与状态定义

const reducer = (state: any, action: Actions) => {
    if (action.type === 'COMPLETE_ORDER') {
        const newOrdersList: OrderType[] = state.orders
        newOrdersList.push({
            orderItems: action.payload.orderItems,
            paymentPreference: action.payload.paymentPreference,
            totalPrice: action.payload.totalPrice,
            date: action.payload.date,
            id: action.payload.id
        })

        console.log(newOrdersList) // (4) [{…}, {…}, {…}, {…}] 
        return {
            orders: newOrdersList,
            cartItems: []
        }
    }  
    return state
};

const [ orderState, dispatch ] = useReducer(reducer, {
    cartItems: [],
    orders: []
});

const completeOrder = (newOrderData: OrderType) => {
    dispatch({
        type: 'COMPLETE_ORDER',
        payload: {
            ...newOrderData
        }
    })
}; 

提交订单的Effect

useEffect(() => {
    const totalPrice = cartItems.reduce(
        (total, currentValue) => total = total + currentValue.price!, 0
    )

    const orderItems = cartItems

    const newOrderData: OrderType = {
        orderItems,
        paymentPreference,
        totalPrice,
        date: format(new Date(), 'dd/MM/yyyy', { locale: ptBR }),
        id: uuid()
    }
    
    completeOrder(newOrderData) 
}, []);

问题原因

  • 状态突变违反React不可变原则:Reducer中const newOrdersList = state.orders是直接引用原状态数组,调用push会直接修改原state.orders,导致React无法正确追踪状态变化,可能重复触发状态更新,多次添加相同订单。
  • useEffect依赖项缺失:Effect中用到了cartItems和paymentPreference但未添加到依赖数组,若组件因其他原因重复渲染,会导致Effect重复执行;加上React严格模式下开发环境会执行两次Effect,进一步加剧重复提交。
  • 自动触发逻辑不合理:用Effect自动提交订单,而非用户手动操作触发,容易因组件生命周期或状态变化导致多次调用completeOrder。

修复方案

1. 修复Reducer,保证状态不可变

在Reducer中创建原数组的浅拷贝,避免直接修改原状态:

const reducer = (state: any, action: Actions) => {
    if (action.type === 'COMPLETE_ORDER') {
        // 浅拷贝原订单数组,避免修改原状态
        const newOrdersList: OrderType[] = [...state.orders];
        newOrdersList.push({
            orderItems: action.payload.orderItems,
            paymentPreference: action.payload.paymentPreference,
            totalPrice: action.payload.totalPrice,
            date: action.payload.date,
            id: action.payload.id
        });

        console.log(newOrdersList);
        // 返回全新的状态对象,保留原状态的其他属性
        return {
            ...state,
            orders: newOrdersList,
            cartItems: []
        };
    }  
    return state;
};

2. 修正Effect逻辑(或改用手动触发)

方案A:调整Effect的依赖与触发条件

如果仍需用Effect触发,需补充依赖项并添加防重复提交判断:

useEffect(() => {
    // 购物车为空时不提交订单
    if (cartItems.length === 0) return;

    const totalPrice = cartItems.reduce(
        (total, currentValue) => total + (currentValue.price || 0), 0
    );

    // 拷贝购物车商品,避免后续状态变化影响订单数据
    const orderItems = [...cartItems];

    const newOrderData: OrderType = {
        orderItems,
        paymentPreference,
        totalPrice,
        date: format(new Date(), 'dd/MM/yyyy', { locale: ptBR }),
        id: uuid()
    };
    
    completeOrder(newOrderData);
}, [cartItems, paymentPreference]); // 添加所有用到的外部依赖

方案B:改用用户手动触发(推荐)

将订单提交逻辑移到按钮点击事件中,避免自动重复触发:

// 订单提交处理函数
const handleSubmitOrder = () => {
    if (cartItems.length === 0) return;

    const totalPrice = cartItems.reduce(
        (total, currentValue) => total + (currentValue.price || 0), 0
    );

    const orderItems = [...cartItems];

    const newOrderData: OrderType = {
        orderItems,
        paymentPreference,
        totalPrice,
        date: format(new Date(), 'dd/MM/yyyy', { locale: ptBR }),
        id: uuid()
    };
    
    completeOrder(newOrderData);
};

// 在组件中添加提交按钮
// <button onClick={handleSubmitOrder}>确认提交订单</button>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 13:22:10