@shopkit/cart
v0.1.2
Published
Cart state management, hooks, and services for e-commerce storefronts
Downloads
298
Maintainers
Readme
@shopkit/cart
Cart state management for e-commerce storefronts.
Installation
npm install @shopkit/cart
# or
bun add @shopkit/cartQuick Start
1. Configure the cart at app startup
// app/providers.tsx or similar
import {
configureCart,
DefaultCartService,
DefaultCartStorage,
} from '@shopkit/cart';
// Configure once at app initialization
configureCart({
merchantName: 'my-store',
cartService: new DefaultCartService('/api/cart'),
cartStorage: new DefaultCartStorage('my-store'),
defaultCurrencyCode: 'USD',
onCartEvent: (event) => {
console.log('Cart event:', event);
},
});2. Initialize the cart in your layout
// app/layout.tsx
import { CartInitializer } from '@shopkit/cart';
export default function RootLayout({ children }) {
return (
<html>
<body>
<CartInitializer merchantName="my-store" />
{children}
</body>
</html>
);
}3. Use the cart store in components
import { useCartStore, mapProductToCartItem, useCartCount, useCartToggle } from '@shopkit/cart';
function ProductCard({ product }) {
const { addItem, loadingItems } = useCartStore();
const cartCount = useCartCount();
const toggleCart = useCartToggle();
const variantId = product.variant?.id || product.variantId;
const isLoading = loadingItems[variantId] || false;
const handleAddToCart = () => {
const cartItem = mapProductToCartItem(product);
addItem(cartItem);
};
return (
<div>
<h3>{product.title}</h3>
<button onClick={handleAddToCart} disabled={isLoading}>
{isLoading ? 'Adding...' : 'Add to Cart'}
</button>
<button onClick={toggleCart}>
Cart ({cartCount})
</button>
</div>
);
}Note: Developers are responsible for mapping product data to the correct
CartItemformat. Use themapProductToCartItemutility for common product structures.
API Reference
Configuration
configureCart(config: CartConfig)
Configure the cart module. Must be called before using any cart functionality.
interface CartConfig {
merchantName: string;
cartService: ICartService;
cartStorage: ICartStorage;
defaultCurrencyCode?: string;
onCartEvent?: (event: CartEvent) => void;
}Store
useCartStore()
The main hook for all cart operations. Provides access to cart state and actions.
State:
| Property | Type | Description |
|----------|------|-------------|
| items | CartItem[] | Items currently in the cart |
| isOpen | boolean | Whether the cart drawer/modal is open |
| isLoading | boolean | Whether any cart operation is in progress |
| loadingItems | Record<string, boolean> | Per-item loading states keyed by variantId |
| error | string \| null | Current error message, if any |
| cartToken | string \| null | Cart token for API authentication |
| checkoutUrl | string \| undefined | URL to proceed to checkout |
| totalAmount | { amount: number; currencyCode: string } \| undefined | Server-calculated total |
| isInitialized | boolean | Whether the cart has been initialized |
Actions:
| Method | Signature | Description |
|--------|-----------|-------------|
| initializeCart | (merchantName?: string) => Promise<void> | Initialize the cart |
| addItem | (item: CartItem) => Promise<void> | Add a single item to cart |
| addItems | (items: CartItemRequest[]) => Promise<void> | Add multiple items to cart |
| removeItem | (lineId: string, variantId?: string) => Promise<void> | Remove an item from cart |
| incrementItem | (lineId: string, variantId?: string) => Promise<void> | Increment item quantity by 1 |
| decrementItem | (lineId: string, variantId?: string) => Promise<void> | Decrement item quantity by 1 |
| setItemQuantity | (lineId: string, quantity: number, variantId?: string) => Promise<void> | Set item quantity directly |
| clearCart | () => Promise<void> | Clear all items from cart |
| openCart | () => Promise<void> | Open the cart drawer/modal |
| closeCart | () => void | Close the cart drawer/modal |
| toggleCart | () => Promise<void> | Toggle cart open/closed |
Example - Cart Drawer:
function CartDrawer() {
const { items, isOpen, closeCart, removeItem, incrementItem, decrementItem, loadingItems } = useCartStore();
if (!isOpen) return null;
return (
<div className="cart-drawer">
<button onClick={closeCart}>Close</button>
{items.map((item) => {
const isItemLoading = loadingItems[item.variantId || item.id] || false;
return (
<div key={item.id}>
<span>{item.title}</span>
<button onClick={() => decrementItem(item.id, item.variantId)} disabled={isItemLoading}>-</button>
<span>{item.quantity}</span>
<button onClick={() => incrementItem(item.id, item.variantId)} disabled={isItemLoading}>+</button>
<button onClick={() => removeItem(item.id, item.variantId)} disabled={isItemLoading}>Remove</button>
</div>
);
})}
</div>
);
}Per-Item Loading States
The loadingItems property tracks loading state for each item individually, allowing you to show loading indicators only for the specific item being modified.
const { loadingItems, addItem, incrementItem } = useCartStore();
// Check if a specific product is loading
const isProductLoading = (variantId: string) => loadingItems[variantId] || false;
// Use in component
function ProductButton({ product }) {
const variantId = product.variant?.id || product.variantId;
const isLoading = isProductLoading(variantId);
return (
<button disabled={isLoading}>
{isLoading ? <Spinner /> : 'Add to Cart'}
</button>
);
}Selector Hooks
Convenience hooks for specific state slices:
| Hook | Return Type | Description |
|------|-------------|-------------|
| useCartCount() | number | Number of unique items in cart |
| useCartTotalQuantity() | number | Total quantity of all items |
| useCartItems() | CartItem[] | Array of cart items |
| useCartToggle() | () => void | Function to toggle cart open/closed |
| useCartStatus() | { isLoading, error } | Loading and error state |
| useCartTotal() | number | Client-calculated total price |
| useCartTotalAmount() | { amount, currencyCode } \| undefined | Server-calculated total |
| useIsInCart(productId, variantId?) | boolean | Check if product is in cart |
| useCartItem(productId, variantId?) | CartItem \| undefined | Get specific cart item |
| useCheckoutUrl() | string \| undefined | Get checkout URL |
Utils
mapProductToCartItem(product, variantId?, quantity?, currencyCode?)
Map a product object to a CartItem. Use this to convert your product data structure to the format required by the cart.
import { mapProductToCartItem } from '@shopkit/cart';
const cartItem = mapProductToCartItem(product);
addItem(cartItem);formatPrice(amount, currencyCode?, locale?)
Format a price amount for display.
formatPrice(29.99, 'USD', 'en-US'); // "$29.99"calculateCartTotal(items)
Calculate the total price of cart items (client-side).
calculateSavings(items)
Calculate total savings from compare-at prices.
Services
DefaultCartService
Default HTTP-based cart service implementation.
const service = new DefaultCartService('/api/cart');DefaultCartStorage
Default localStorage-based cart storage.
const storage = new DefaultCartStorage('my-store');Components
<CartInitializer merchantName="..." />
Initialize the cart on mount. Must be rendered after configureCart() is called.
Custom Services
Implement ICartService for custom cart backends:
import { ICartService, CartResponse, CartItemRequest } from '@shopkit/cart';
class MyCartService implements ICartService {
async createCart(merchantName: string): Promise<CartResponse> {
// Your implementation
}
async getCart(cartId: string): Promise<CartResponse> {
// Your implementation
}
async addToCart(cartId: string, items: CartItemRequest[]): Promise<void> {
// Your implementation
}
async removeFromCart(cartId: string, itemIds: string[]): Promise<void> {
// Your implementation
}
async updateCart(cartId: string, items: CartItemRequest[]): Promise<void> {
// Your implementation
}
}License
MIT
