AddToCart
Description
A headless render props component that provides state and actions for adding products to the cart. It handles cart dispatch, stock validation, loading states, and error management — but renders no UI of its own.
Role in Theming
AddToCart is one of EverShop's headless components — it owns the business logic while leaving all UI decisions to its parent. This design exists specifically for theme development:
- AddToCart renders nothing. It returns only what its
childrenfunction renders. - Theme developers do not override AddToCart. Instead, they override the parent components that consume it (
ProductSingleForm,ProductListItemRender). - The logic stays stable across themes. Cart dispatch, stock checks, error handling, and loading state management are encapsulated in AddToCart. Theme changes cannot break cart behavior.
This creates a clean boundary:
┌──────────────────────────────────────────────────┐
│ Theme layer (what you override) │
│ ProductSingleForm / ProductListItemRender │
│ — layout, styling, animations, custom UI │
├────────────────────────────────── ────────────────┤
│ Core layer (stable contract) │
│ AddToCart │
│ — cart dispatch, validation, error handling │
└──────────────────────────────────────────────────┘
Theme Override Points
AddToCart is consumed by these components, which are the actual override targets for theme developers:
| Parent Component | Route | Override Path in Theme |
|---|---|---|
| ProductSingleForm | productView | themes/<name>/src/pages/productView/ProductSingleForm.tsx |
| ProductListItemRender | categoryView, catalogSearch | themes/<name>/src/pages/categoryView/ProductListItemRender.tsx |
Import
import { AddToCart } from '@components/frontStore/cart/AddToCart';
Usage
import { AddToCart } from '@components/frontStore/cart/AddToCart';
function ProductButton({ product }) {
return (
<AddToCart product={product} qty={1}>
{(state, actions) => (
<button
onClick={actions.addToCart}
disabled={!state.canAddToCart || state.isLoading}
>
{state.isLoading ? 'Adding...' : 'Add to Cart'}
</button>
)}
</AddToCart>
);
}
Props
| Name | Type | Required | Description |
|---|---|---|---|
| product | ProductInfo | Yes | Product information (sku, isInStock) |
| qty | number | Yes | Quantity to add |
| onSuccess | (qty: number) => void | No | Callback on successful add |
| onError | (error: string) => void | No | Callback on error |
| children | (state: AddToCartState, actions: AddToCartActions) => ReactNode | Yes | Render function receiving state and actions |
ProductInfo Interface
interface ProductInfo {
sku: string; // Product SKU
isInStock: boolean; // Stock availability
}
State Object
The render function receives a state object:
interface AddToCartState {
isLoading: boolean; // True when adding to cart
error: string | null; // Error message if any
canAddToCart: boolean; // True if product can be added
isInStock: boolean; // Stock availability
}
Actions Object
The render function receives an actions object:
interface AddToCartActions {
addToCart: () => Promise<void>; // Add product to cart
clearError: () => void; // Clear error state
}
Examples
Basic Button
import { AddToCart } from '@components/frontStore/cart/AddToCart';
function BuyButton({ product }) {
return (
<AddToCart product={product} qty={1}>
{(state, actions) => (
<button
onClick={actions.addToCart}
disabled={!state.canAddToCart || state.isLoading}
>
{state.isLoading ? 'Adding...' : 'Add to Cart'}
</button>
)}
</AddToCart>
);
}
With Error Display and Callbacks
import { AddToCart } from '@components/frontStore/cart/AddToCart';
function ProductAddToCart({ product }) {
const handleSuccess = (qty) => {
// Show toast notification
};
const handleError = (error) => {
// Show error notification
};
return (
<AddToCart
product={product}
qty={1}
onSuccess={handleSuccess}
onError={handleError}
>
{(state, actions) => (
<div>
<button
onClick={actions.addToCart}
disabled={!state.canAddToCart || state.isLoading}
>
{state.isLoading ? 'Adding...' : 'Add to Cart'}
</button>
{state.error && (
<div className="error">
{state.error}
<button onClick={actions.clearError}>Dismiss</button>
</div>
)}
{!state.isInStock && (
<p className="out-of-stock">Out of Stock</p>
)}
</div>
)}
</AddToCart>
);
}
Theme Override Example
A theme developer overrides ProductSingleForm to provide a completely custom add-to-cart experience. Create this file in your theme:
themes/my-theme/src/pages/productView/ProductSingleForm.tsx
import { AddToCart } from '@components/frontStore/cart/AddToCart';
import { useState } from 'react';
function ProductSingleForm({ product }) {
const [qty, setQty] = useState(1);
return (
<div className="my-theme-product-form">
<div className="quantity-picker">
<button onClick={() => setQty(Math.max(1, qty - 1))}>-</button>
<span>{qty}</span>
<button onClick={() => setQty(qty + 1)}>+</button>
</div>
<AddToCart
product={{ sku: product.sku, isInStock: product.inventory.isInStock }}
qty={qty}
onSuccess={() => {
// Custom success animation
}}
>
{(state, actions) => (
<button
className="my-theme-add-btn"
onClick={actions.addToCart}
disabled={!state.canAddToCart || state.isLoading}
>
{state.isLoading && <span className="spinner" />}
{!state.isLoading && state.isInStock && 'Add to Cart'}
{!state.isInStock && 'Sold Out'}
</button>
)}
</AddToCart>
</div>
);
}
export default ProductSingleForm;
export const layout = {
areaId: 'productPageMiddleRight',
sortOrder: 30
};
The theme version completely replaces the core ProductSingleForm UI, but reuses AddToCart for all cart logic.
Features
- Headless: Renders no UI — full control via render props
- Loading State: Automatic loading state management
- Error Handling: Built-in error management with clearError action
- Validation: Checks stock availability and quantity
- Callbacks: onSuccess and onError hooks for parent composition
- Type Safe: Full TypeScript support
- Cart Integration: Uses CartContext automatically
Related Components
- CartContext - Shopping cart context
- CartItems - Cart items display component
- Coupon - Coupon management component
Support us
EverShop is an open-source project that relies on community support. If you find our project useful, please consider sponsoring us.