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({
fullName: 'John Doe',
address1: '123 Main St',
city: 'New York',
province: 'NY',
postcode: '10001',
country: 'US',
telephone: '555-0100'
});
addBillingAddress
Add billing address:
const { addBillingAddress } = useCartDispatch();
await addBillingAddress({
fullName: 'John Doe',
address1: '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 shipping method:
const { addShippingMethod } = useCartDispatch();
await addShippingMethod('standard', 'Standard Shipping');
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:
const { fetchAvailableShippingMethods } = useCartDispatch();
await fetchAvailableShippingMethods({
country: 'US',
province: 'CA',
postcode: '90210'
});
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
Related Components
- AppProvider - App context provider
- useAppState - App state hook