Hook for executing product-scoped cart mutations with automatic cart item detection and WooGraphQL Pro support.
A hook for executing cart mutations scoped to a specific product. Works with the SessionProvider context to provide automatic cart item detection and mutation handling.
useCartMutations simplifies cart operations by:
isExecuting()import { useCartMutations } from '@woographql/react-hooks'; function AddToCartButton({ productId }: { productId: number }) { const { quantityFound, mutate, fetching } = useCartMutations({ productId }); const handleClick = () => { if (quantityFound) { mutate('removeItemsFromCart', {}); } else { mutate('addToCart', { quantity: 1 }); } }; return ( <button onClick={handleClick} disabled={fetching}> {fetching ? 'Loading...' : quantityFound ? 'Remove from Cart' : 'Add to Cart'} </button> ); }
function useCartMutations<T extends SessionData>( product: { productId: number; variationId?: number; variation?: { attributeName: string; attributeValue: string }[]; extraData?: string; }, context?: React.Context<T> ): { fetching: boolean; quantityFound: number; key?: string; mutate: (mutation?: string, input: CartMutationInput) => Promise<Cart | null | undefined>; };
| Parameter | Type | Description |
|---|---|---|
product.productId | number | The product's database ID |
product.variationId | number | Optional variation ID for variable products |
product.variation | array | Optional variation attributes |
product.extraData | string | Optional JSON string for custom cart item data |
context | Context | Optional custom session context (defaults to sessionContext) |
| Property | Type | Description |
|---|---|---|
fetching | boolean | True when any cart operation is in progress |
quantityFound | number | Quantity of this product currently in the cart |
key | string | The cart item key (for updates/removals) |
mutate | function | Execute a cart mutation |
Add a simple or variable product to cart:
const { mutate } = useCartMutations({ productId: 123 }); // Simple product mutate('addToCart', { quantity: 2 }); // Variable product const { mutate } = useCartMutations({ productId: 123, variationId: 456, variation: [ { attributeName: 'pa_color', attributeValue: 'blue' }, { attributeName: 'pa_size', attributeValue: 'large' }, ], }); mutate('addToCart', { quantity: 1 });
Remove the product from cart:
const { quantityFound, mutate } = useCartMutations({ productId: 123 }); if (quantityFound) { mutate('removeItemsFromCart', {}); }
Update the quantity of an item in cart:
const { quantityFound, mutate } = useCartMutations({ productId: 123 }); if (quantityFound) { mutate('updateItemQuantities', { quantity: 5 }); }
Add a bundle product with selected bundle items:
import { useCartMutations } from '@woographql/react-hooks'; function BundleAddToCart({ product }) { const { databaseId, bundleItems } = product; const { quantityFound, mutate, fetching } = useCartMutations({ productId: databaseId }); const [selectedItems, setSelectedItems] = useState<Record<number, number>>({}); const addBundle = () => { mutate('addBundleToCart', { quantity: 1, bundleItems: bundleItems.edges.map(({ bundledItemId, optional }) => ({ bundleItemId: bundledItemId, optionalSelected: optional && selectedItems[bundledItemId] > 0, quantity: selectedItems[bundledItemId] || (optional ? 0 : 1), })), }); }; const removeBundle = () => { mutate('removeItemsFromCart', {}); }; return ( <button onClick={quantityFound ? removeBundle : addBundle} disabled={fetching}> {quantityFound ? 'Remove Bundle' : 'Add Bundle'} </button> ); }
GraphQL Mutation:
mutation AddBundleToCart($input: AddBundleToCartInput!) { addBundleToCart(input: $input) { cart { contents { itemCount } total } cartItem { key ... on BundleCartItem { bundledItems { product { node { name } } quantity } } } } }
Add a composite product with component selections:
import { useCartMutations } from '@woographql/react-hooks'; function CompositeAddToCart({ product }) { const { databaseId, components } = product; const { quantityFound, mutate, fetching } = useCartMutations({ productId: databaseId }); const [configuration, setConfiguration] = useState<Record<string, { componentId: string; productId?: number; quantity: number; }>>({}); const selectComponent = (componentId: string, productId: number, quantity = 1) => { setConfiguration(prev => ({ ...prev, [componentId]: { componentId, productId, quantity }, })); }; const addComposite = () => { mutate('addCompositeToCart', { configuration: Object.values(configuration), }); }; return ( <div> {components.map(component => ( <div key={component.componentId}> <h3>{component.title}</h3> {component.queryOptions.map(option => ( <button key={option.databaseId} onClick={() => selectComponent( String(component.componentId), option.databaseId, 1 )} > {option.name} </button> ))} </div> ))} <button onClick={addComposite} disabled={fetching}> Add to Cart </button> </div> ); }
GraphQL Mutation:
mutation AddCompositeToCart($input: AddCompositeToCartInput!) { addCompositeToCart(input: $input) { cart { contents { itemCount } total } cartItem { key ... on CompositeCartItem { components { component { title } product { node { name } } quantity } } } } }
Add a product with add-on selections:
import { useCartMutations, useProduct } from '@woographql/react-hooks'; function ProductWithAddons({ productId }) { const { mutate, fetching } = useCartMutations({ productId }); const { get } = useProduct(); const addons = get('addons'); const [selectedAddons, setSelectedAddons] = useState<Record<string, string[]>>({}); const handleAddonChange = (fieldName: string, values: string[]) => { setSelectedAddons(prev => ({ ...prev, [fieldName]: values })); }; const addToCart = () => { mutate('addToCart', { quantity: 1, addons: Object.entries(selectedAddons).map(([fieldName, value]) => ({ fieldName, value, })), }); }; return ( <div> {addons?.map(addon => ( <div key={addon.fieldName}> <label>{addon.name}</label> {addon.options?.map(option => ( <label key={option.label}> <input type="checkbox" onChange={(e) => { const current = selectedAddons[addon.fieldName] || []; handleAddonChange( addon.fieldName, e.target.checked ? [...current, option.label] : current.filter(v => v !== option.label) ); }} /> {option.label} (+{option.price}) </label> ))} </div> ))} <button onClick={addToCart} disabled={fetching}> Add to Cart </button> </div> ); }
GraphQL Input:
const input = { productId: 123, quantity: 1, addons: [ { fieldName: 'addon-service-type', value: ['Premium'] }, { fieldName: 'addon-extras', value: ['Gift Wrap', 'Extended Warranty'] }, ], };
For cart-level operations (coupons, shipping), use the useOtherCartMutations hook:
import { useOtherCartMutations } from '@woographql/react-hooks'; function CartControls() { const { applyCoupon, removeCoupon, setShippingLocale, setShippingMethod, applyingCoupon, removingCoupon, savingShippingInfo, } = useOtherCartMutations(); return ( <div> <button onClick={() => applyCoupon('SUMMER20')} disabled={applyingCoupon} > Apply Coupon </button> <button onClick={() => setShippingLocale({ postcode: '90210', country: 'US', state: 'CA', })} disabled={savingShippingInfo} > Calculate Shipping </button> </div> ); }
| Property | Type | Description |
|---|---|---|
applyCoupon | (code: string) => Promise<void> | Apply a coupon code |
removeCoupon | (code: string) => Promise<void> | Remove a coupon code |
setShippingLocale | (input) => Promise<void> | Set shipping address for calculations |
setShippingMethod | (method: string) => Promise<void> | Select a shipping method |
applyingCoupon | boolean | Coupon is being applied |
removingCoupon | boolean | Coupon is being removed |
savingShippingInfo | boolean | Shipping info is being saved |
If you have a custom session context, pass it to the hook:
import { useCartMutations } from '@woographql/react-hooks'; import { customSessionContext } from './CustomSessionProvider'; function MyComponent({ productId }) { const { mutate } = useCartMutations( { productId }, customSessionContext ); // ... }