React useEffect中购物车ID未定义导致Checkout Token生成失败
解决Commerce.js + React Context购物车ID初始化延迟导致无法生成Checkout Token的问题
问题根源
你的代码存在异步操作的时序冲突:
CartProvider中的getCart是异步请求,组件挂载后才发起,需要一定时间返回数据并更新购物车状态。- 结账页面的
useEffect依赖项为空数组,仅在组件首次挂载时执行一次,此时购物车id还是初始状态的空字符串,导致生成Token失败。而页面能渲染出id是因为后续购物车数据更新触发组件重渲染,此时id已有有效值。
解决方案
方案一:监听购物车ID变化,有效时生成Token
修改结账页面的useEffect,添加id作为依赖项,并在生成Token前校验id的有效性:
const CheckoutPage = () => { const [open, setOpen] = useState(false); const [selectedDeliveryMethod, setSelectedDeliveryMethod] = useState( deliveryMethods[0] ); const [checkoutToken, setCheckoutToken] = useState(null); const { line_items, id } = useCartState(); useEffect(() => { const generateToken = async () => { // 跳过空ID的情况,避免无效请求 if (!id) return; try { const token = await commerce.checkout.generateToken(id, { type: 'cart', }); setCheckoutToken(token); } catch (error) { console.error('生成Checkout Token失败:', error); } }; generateToken(); }, [id]); // 当id变化时重新执行副作用 return <div> {id} </div>; };
方案二:等待购物车加载完成后再渲染结账页面
在CartProvider中添加加载状态,确保购物车数据完全加载后再处理结账逻辑:
1. 修改CartProvider代码
import { createContext, useEffect, useContext, useReducer, useState } from 'react'; import { commerce } from '../../lib/commerce'; const CartStateContext = createContext(); const CartDispatchContext = createContext(); const CartLoadingContext = createContext(); // 新增加载状态上下文 const SET_CART = 'SET_CART'; const initialState = { id: '', total_items: 0, total_unique_items: 0, subtotal: [], line_items: [{}], }; const reducer = (state, action) => { switch (action.type) { case SET_CART: return { ...state, ...action.payload }; default: throw new Error(`Unknown action: ${action.type}`); } }; export const CartProvider = ({ children }) => { const [state, dispatch] = useReducer(reducer, initialState); const [isLoading, setIsLoading] = useState(true); // 购物车加载状态 const setCart = (payload) => dispatch({ type: SET_CART, payload }); useEffect(() => { getCart(); }, []); const getCart = async () => { try { const cart = await commerce.cart.retrieve(); setCart(cart); } catch (error) { console.log('获取购物车失败:', error); } finally { setIsLoading(false); // 请求结束后标记加载完成 } }; return ( <CartDispatchContext.Provider value={{ setCart }}> <CartLoadingContext.Provider value={isLoading}> <CartStateContext.Provider value={state}> {children} </CartStateContext.Provider> </CartLoadingContext.Provider> </CartDispatchContext.Provider> ); }; export const useCartState = () => useContext(CartStateContext); export const useCartDispatch = () => useContext(CartDispatchContext); export const useCartLoading = () => useContext(CartLoadingContext); // 导出加载状态钩子
2. 在结账页面使用加载状态
const CheckoutPage = () => { const isLoading = useCartLoading(); const [open, setOpen] = useState(false); const [selectedDeliveryMethod, setSelectedDeliveryMethod] = useState( deliveryMethods[0] ); const [checkoutToken, setCheckoutToken] = useState(null); const { line_items, id } = useCartState(); useEffect(() => { const generateToken = async () => { try { const token = await commerce.checkout.generateToken(id, { type: 'cart', }); setCheckoutToken(token); } catch (error) { console.error('生成Checkout Token失败:', error); } }; // 购物车加载完成后再生成Token if (!isLoading) { generateToken(); } }, [id, isLoading]); // 加载中显示提示 if (isLoading) { return <div>加载中...</div>; } return <div> {id} </div>; };
内容的提问来源于stack exchange,提问作者rohit
相关产品推荐
相关产品推荐

