Skip to main content

Cart Context

Description​

A React context that manages the shopping cart state and provides methods for cart operations like adding items, updating quantities, applying coupons, and managing checkout information.

Import​

import { CartProvider, useCartState, useCartDispatch } from '@components/frontStore/cart/CartContext';

Setup​

Wrap your application with the CartProvider:

import { CartProvider } from '@components/frontStore/cart/CartContext';

function App({ cart, query, addMineCartItemApi }) {
return (
<CartProvider
cart={cart}
query={query}
addMineCartItemApi={addMineCartItemApi}
>
{/* Your app components */}
</CartProvider>
);
}

Hooks​

useCartState​

Access cart state and data:

import { useCartState } from '@components/frontStore/cart/CartContext';

function CartSummary() {
const { data, loading, loadingStates } = useCartState();

return (
<div>
<p>Items: {data.totalQty}</p>
<p>Total: {data.grandTotal.text}</p>
{loading && <span>Loading...</span>}
</div>
);
}

useCartDispatch​

Access cart operations:

import { useCartDispatch } from '@components/frontStore/cart/CartContext';

function AddToCartButton({ sku }) {
const { addItem } = useCartDispatch();

const handleAdd = async () => {
await addItem({ sku, qty: 1 });
};

return <button onClick={handleAdd}>Add to Cart</button>;
}

Cart State Structure​

interface CartState {
data: CartData; // Cart data
loading: boolean; // Overall loading state
loadingStates: { // Specific loading states
addingItem: boolean;
removingItem: string | null;
updatingItem: string | null;
addingPaymentMethod: boolean;
addingShippingMethod: boolean;
addingShippingAddress: boolean;
addingBillingAddress: boolean;
addingContactInfo: boolean;
applyingCoupon: boolean;
removingCoupon: boolean;
fetchingShippingMethods: boolean;
};
syncStatus: {
syncing: boolean; // Sync in progress
synced: boolean; // Last sync successful
trigger?: string; // What triggered the sync
};
}

Cart Operations​

addItem​

Add a product to the cart:

const { addItem } = useCartDispatch();

await addItem({ sku: 'PROD-123', qty: 2 });

removeItem​

Remove an item from the cart:

const { removeItem } = useCartDispatch();
const { data } = useCartState();

const handleRemove = async (itemId) => {
await removeItem(itemId);
};

updateItem​

Update item quantity:

const { updateItem } = useCartDispatch();

// Increase quantity
await updateItem(itemId, { qty: 1, action: 'increase' });

// Decrease quantity
await updateItem(itemId, { qty: 1, action: 'decrease' });

addShippingAddress​

Add shipping address:

const { addShippingAddress } = useCartDispatch();

await addShippingAddress({
full_name: 'John Doe',
address_1: '123 Main St',
city: 'New York',
province: 'NY',
postcode: '10001',
country: 'US',
telephone: '555-0100'
});
The Address type is snake_case

The keys are full_name, address_1, address_2, city, province, postcode, country, telephone. Passing camelCase (fullName, address1) does not silently save the wrong shape — validateAddress rejects the call with Full name is required.

addBillingAddress​

Add billing address:

const { addBillingAddress } = useCartDispatch();

await addBillingAddress({
full_name: 'John Doe',
address_1: '123 Main St',
city: 'New York',
province: 'NY',
postcode: '10001',
country: 'US',
telephone: '555-0100'
});

addPaymentMethod​

Select payment method:

const { addPaymentMethod } = useCartDispatch();

await addPaymentMethod('stripe', 'Credit Card');

addShippingMethod​

Select a shipping method. The signature is addShippingMethod(code, name, providerCode) — all three arguments are required:

const { data } = useCartState();
const { addShippingMethod } = useCartDispatch();

// Always thread the values straight off `availableShippingMethods`.
const method = data.availableShippingMethods[0];

await addShippingMethod(method.code, method.name, method.providerCode);
providerCode is required

addShippingMethod throws Cannot add shipping method: providerCode is required when the third argument is missing or empty — there is no silent fallback to 'core'.

providerCode identifies which shipping provider the method came from (core, USPS, EasyPost, …). The server uses it to route validation to that provider's validateMethod; passing the wrong one makes the selection fail with "method no longer available". Read it from availableShippingMethods[].providerCode — the GraphQL field is non-nullable (String!), so it is always present.

addContactInfo​

Add customer contact information:

const { addContactInfo } = useCartDispatch();

await addContactInfo({ email: 'customer@example.com' });

applyCoupon​

Apply a discount coupon:

const { applyCoupon } = useCartDispatch();

await applyCoupon('SUMMER20');

removeCoupon​

Remove applied coupon:

const { removeCoupon } = useCartDispatch();

await removeCoupon();

fetchAvailableShippingMethods​

Fetch shipping methods for an address. Signature: fetchAvailableShippingMethods(params: ShippingAddressParams).

const { fetchAvailableShippingMethods } = useCartDispatch();
const { data } = useCartState();

await fetchAvailableShippingMethods({
country: 'US',
province: 'CA', // optional
postcode: '90210' // optional
});

// The result is written back onto cart state:
data.availableShippingMethods.forEach((method) => {
console.log(method.providerCode, method.code, method.name, method.cost?.text);
});

It runs a GraphQL query, writes the result to data.availableShippingMethods, and toggles loadingStates.fetchingShippingMethods. Each entry has this shape:

interface AvailableShippingMethod {
providerCode: string; // required — pass through to addShippingMethod
code: string;
name: string;
cost?: {
value: number;
text: string;
};
}
info

Throws Cannot fetch shipping methods: cart not initialized when the cart has no UUID yet.

Helper Methods​

isShippingRequired​

Check if cart requires shipping:

const { isShippingRequired } = useCartDispatch();

if (isShippingRequired()) {
// Show shipping form
}

isReadyForCheckout​

Check if cart is ready for checkout:

const { isReadyForCheckout } = useCartDispatch();

const canCheckout = isReadyForCheckout();

getErrors​

Get validation errors:

const { getErrors } = useCartDispatch();

const errors = getErrors();
errors.forEach(error => {
console.log(error.message);
});

getId​

Get cart UUID:

const { getId } = useCartDispatch();

const cartId = getId();

clearError​

Clear error state:

const { clearError } = useCartDispatch();

clearError();

Complete Example​

import { useCartState, useCartDispatch } from '@components/frontStore/cart/CartContext';

function CartPage() {
const { data, loading, loadingStates } = useCartState();
const { removeItem, updateItem, applyCoupon } = useCartDispatch();

const handleRemove = async (itemId) => {
try {
await removeItem(itemId);
} catch (error) {
console.error('Failed to remove item:', error);
}
};

const handleUpdateQty = async (itemId, action) => {
try {
await updateItem(itemId, { qty: 1, action });
} catch (error) {
console.error('Failed to update quantity:', error);
}
};

return (
<div>
<h1>Shopping Cart</h1>

{loading && <div>Loading...</div>}

{data.items.map(item => (
<div key={item.cartItemId}>
<h3>{item.productName}</h3>
<p>Price: {item.productPrice.text}</p>
<p>Qty: {item.qty}</p>

<button
onClick={() => handleUpdateQty(item.cartItemId, 'increase')}
disabled={loadingStates.updatingItem === item.cartItemId}
>
+
</button>

<button
onClick={() => handleUpdateQty(item.cartItemId, 'decrease')}
disabled={loadingStates.updatingItem === item.cartItemId}
>
-
</button>

<button
onClick={() => handleRemove(item.cartItemId)}
disabled={loadingStates.removingItem === item.cartItemId}
>
Remove
</button>
</div>
))}

<div>
<h3>Summary</h3>
<p>Subtotal: {data.subTotal.text}</p>
<p>Shipping: {data.shippingFeeExclTax.text}</p>
<p>Tax: {data.taxAmount.text}</p>
<p>Total: {data.grandTotal.text}</p>
</div>
</div>
);
}

Loading States​

Use specific loading states for better UX:

const { loadingStates } = useCartState();

// Check if adding item
if (loadingStates.addingItem) {
// Show loading spinner
}

// Check if specific item is being removed
if (loadingStates.removingItem === itemId) {
// Show loading on that item
}

// Check if updating specific item
if (loadingStates.updatingItem === itemId) {
// Show loading on that item
}

Error Handling​

const { data } = useCartState();
const { clearError } = useCartDispatch();

useEffect(() => {
if (data.error) {
alert(data.error);
clearError();
}
}, [data.error]);

Sync Status​

Monitor cart synchronization with server:

const { syncStatus } = useCartState();

if (syncStatus.syncing) {
// Show syncing indicator
}

if (syncStatus.synced) {
// Show success message
}

// Check what triggered the sync
console.log(syncStatus.trigger); // e.g., 'addItem', 'applyCoupon'

Features​

  • State Management: Centralized cart state with React Context
  • Loading States: Granular loading states for each operation
  • Error Handling: Built-in error management
  • Retry Logic: Automatic retry for failed requests
  • Sync Status: Track synchronization with server
  • Type Safe: Full TypeScript support
  • Extensible: Support for custom fields via extensions
  • GraphQL Integration: Uses urql for data fetching