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

Redux状态更新后无法立即获取最新值的问题求助

解决Redux中点击按钮后购物车数量需两次点击才更新的问题

问题描述

我有一个使用Redux进行状态管理的React应用。需求是:点击「立即购买」按钮,dispatch一个action向服务器发送请求添加商品(根据服务器响应更新购物车商品数量),随后检查状态中cartItemCount是否大于0,若满足则执行相应操作。

但目前出现问题:需要点击两次按钮,cartItemCount才会显示为1。已排除首次API请求失败的可能。

相关代码

CourseSpecificScreen.tsx

const mapStateToProps = (state: RootState) => {
    return {
        courseSpecificReducer: state.courseSpecificReducer,
        authState: state.authReducer,
        currencyState: state.currencyReducer,
        cartReducer: state.cartReducer,
        courseCategoriesState: state.courseCategoriesReducer,
    };
};

const mapDispatchTopProps = (dispatch: Dispatch<AnyAction>) => {
    return bindActionCreators(ActionCreators, dispatch);
};

const connector = connect(mapStateToProps, mapDispatchTopProps);

type CourseSpecificScreenNavigationProp = CompositeNavigationProp<
    StackNavigationProp<ExploreRouteStackParamList, "CourseSpecificScreen">,
    CompositeNavigationProp<
        StackNavigationProp<AppRouteHeaderParamList>,
        StackNavigationProp<AuthRouteStackParamList>
    >
>;
type CourseSpecificScreenRouteProp = RouteProp<
    ExploreRouteStackParamList,
    "CourseSpecificScreen"
>;

type Props = PropsFromRedux & {
    navigation: CourseSpecificScreenNavigationProp;
    route: CourseSpecificScreenRouteProp;
};

type State = {
    cartItemCount: number;
};

class CourseSpecificScreen extends Component<Props, State> {
    pruchaseItem = async () => {
        const {
            courseSpecificReducer,
            clearCartAndAddItem,
            navigation,
            cartReducer,
            getCartItemCount,
        } = this.props;

        const paymentMethod = paymentMethodForDevice();

        await clearCartAndAddItem(  
            paymentMethod, 
            courseSpecificReducer.productData.code as string,
            1,
            navigation
        )
        
        if(cartReducer.cartItemCount > 0) {
         // do some stuff
        }
    };

    render() {
        return (
            <Button
                btnStyle={[this.getStyles().smallButtonBuyCourse]}
                labelStyle={[this.getStyles().buttonStickyLabelStyle]}
                label={translate(
                    productData.isBundle && productData.isBundle === true
                        ? "CategorySpecificScreen_buyThisBundle"
                        : "CategorySpecificScreen_buyThisCourse",
                )}
                onPress={this.purchaseItem}
                disabled={false}
            />
        );
    };
}

CourseSpecificScreen.contextType = LocalizationContext;
export default connector(CourseSpecificScreen);

ThunkActions.ts

export const clearCartAndAddItem = (
    paymentMethod: string,
    productCode: string,
    quantity: number,
    navigation: any,
): AppThunk => {
    return async (dispatch) => {
        dispatch(cartActions.updateCartLoadingStatus(true));
        const response = await cartServices.clearCart();
        const {httpStatusCode} = response as APIResponse;
        
        switch (httpStatusCode) {
            case httpStatusCodes.SUCCESS_OK:
            case httpStatusCodes.SUCCESS_CREATED:
            case httpStatusCodes.SUCCESS_NO_CONTENT:    
                dispatch(cartActions.updateCartLoadingStatus(false));
                dispatch(cartActions.updateCartItemCount(0))
                globalConfig.setCartItemCount(0);
                dispatch(addItemToCart(paymentMethod, productCode, quantity, navigation));
                break;
            case httpStatusCodes.CLIENT_ERROR_UNAUTHORIZED:
            case httpStatusCodes.SERVER_ERROR_INTERNAL_SERVER_ERROR:
                dispatch(cartActions.updateCartLoadingStatus(false));
                let alertMessage = "Error, please try again later.";
                if (response?.message) alertMessage = response?.message;
                Alert.alert("Alert", alertMessage, [
                    {
                        text: "Ok",
                    },
                ]);
                break;
            default: {
                dispatch(cartActions.updateCartLoadingStatus(false));
            }
        }
    };
};

export const addItemToCart = (
    paymentMethod: string,
    productCode: string,
    quantity: number,
    navigation: any,
): AppThunk => {
    return async (dispatch) => {
        dispatch(cartActions.updateCartLoadingStatus(true));
        const response = await cartServices.addItemToCart(productCode, quantity, paymentMethod);
        const {httpStatusCode, data, error, message} = response as APIResponse;
        
        console.log('add_item_to_cart_response:', response);
        
        switch (httpStatusCode) {
            case httpStatusCodes.SUCCESS_OK:
            case httpStatusCodes.SUCCESS_CREATED:
                dispatch(cartActions.updateCartLoadingStatus(false));
                dispatch(cartActions.updateCartItemCount(quantity));
                globalConfig.setCartItemCount(quantity);
                break;
            case httpStatusCodes.CLIENT_ERROR_UNAUTHORIZED:
                dispatch(cartActions.updateCartLoadingStatus(false));
                break;
            case httpStatusCodes.SERVER_ERROR_INTERNAL_SERVER_ERROR:
            case httpStatusCodes.CLIENT_ERROR_BAD_REQUEST:
                dispatch(cartActions.updateCartLoadingStatus(false));
                Alert.alert("Alert", (message)? message : "Error, it looks like you already have access to this course.", [
                    {
                        text: "Ok",
                    },
                ]);
                break;
            default: {
                dispatch(cartActions.updateCartLoadingStatus(false));
            }
        }
    };
};

Reducers.ts

const initialState: CartInitialState = {
    isLoading: true,
    cartToken: "",
    responseStatus: apiResponseStatuses.IDLE,
    cartItemCount: 0,
    isMessageVisible: false,
    message: "",
};

export default function cartReducer(
    state = initialState,
    action: CartActionTypes,
): CartInitialState {
    switch (action.type) {
        case UPDATE_LOADING_STATUS:
            return {
                ...state,
                isLoading: action.isLoading,
            };
        case UPDATE_CART_TOKEN:
            return {
                ...state,
                cartToken: action.cartToken,
            };
        case UPDATE_RESPONSE_STATUS:
            return {
                ...state,
                responseStatus: action.responseStatus,
            };
        case UPDATE_CART_ITEM_COUNT_TOKEN:
            return {
                ...state,
                cartItemCount: action.cartItemCount,
            };

        case CLEAR_DATA_ON_LOGOUT:
            return {
                ...state,
                isLoading: true,
                cartToken: "",
                responseStatus: apiResponseStatuses.IDLE,
                cartItemCount: 0,
                isMessageVisible: false,
                message: "",
            };

        default: {
            return state;
        }
    }
}

原因分析

  1. 异步action未完整等待:clearCartAndAddItem中dispatchaddItemToCart时没有用await,导致purchaseItem里的await clearCartAndAddItem仅等待了清空购物车的操作,没有等待添加商品的异步流程完成,此时Redux状态还未更新,读取的是旧值。
  2. 组件props不会即时更新:即使所有异步action执行完毕,React组件的props不会在当前函数调用中立即刷新,purchaseItem里的cartReducer还是当前渲染周期的旧状态,必须等组件重新渲染后才能获取新值。

解决方案

方案一:修正thunk中的异步等待逻辑

修改clearCartAndAddItem,对dispatch(addItemToCart(...))添加await,确保添加商品的异步流程完全执行完毕再结束当前thunk:

// ThunkActions.ts 中修改clearCartAndAddItem的成功分支
case httpStatusCodes.SUCCESS_OK:
case httpStatusCodes.SUCCESS_CREATED:
case httpStatusCodes.SUCCESS_NO_CONTENT:    
    dispatch(cartActions.updateCartLoadingStatus(false));
    dispatch(cartActions.updateCartItemCount(0))
    globalConfig.setCartItemCount(0);
    // 等待addItemToCart的异步流程完成
    await dispatch(addItemToCart(paymentMethod, productCode, quantity, navigation));
    break;

方案二:通过组件生命周期监听状态变化

由于组件props不会在函数调用中即时更新,可在componentDidUpdate中监听cartItemCount的变化,当它从0变为大于0时执行后续操作:

// CourseSpecificScreen.tsx 中添加componentDidUpdate方法
componentDidUpdate(prevProps: Props) {
    const { cartReducer } = this.props;
    // 检测购物车数量从0变为正数的变化
    if (prevProps.cartReducer.cartItemCount === 0 && cartReducer.cartItemCount > 0) {
        // 执行原本要做的操作
        // do some stuff
    }
}

同时修改purchaseItem,移除内部的状态检查,只负责触发action:

pruchaseItem = async () => {
    const {
        courseSpecificReducer,
        clearCartAndAddItem,
        navigation,
    } = this.props;

    const paymentMethod = paymentMethodForDevice();

    await clearCartAndAddItem(  
        paymentMethod, 
        courseSpecificReducer.productData.code as string,
        1,
        navigation
    )
};

额外验证点

确认action类型与reducer匹配:检查cartActions.updateCartItemCount的type是否为UPDATE_CART_ITEM_COUNT_TOKEN,确保reducer能正确接收并更新cartItemCount:

// 示例action定义,确保type一致
export const updateCartItemCount = (cartItemCount: number) => ({
    type: UPDATE_CART_ITEM_COUNT_TOKEN,
    cartItemCount
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 02:57:14