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; } } }
原因分析
- 异步action未完整等待:
clearCartAndAddItem中dispatchaddItemToCart时没有用await,导致purchaseItem里的await clearCartAndAddItem仅等待了清空购物车的操作,没有等待添加商品的异步流程完成,此时Redux状态还未更新,读取的是旧值。 - 组件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
相关产品推荐
相关产品推荐

