A production-ready Shopify Storefront API cart library with optimistic updates, complete Cart API coverage, and zero-config React integration. Built for Next.js, React Router, TanStack Start, Expo, Vite, and plain React.
npm install @trackit.io/shopify-cartyarn add @trackit.io/shopify-cartpnpm add @trackit.io/shopify-cartTry the live demos at shopify-cart.trackit.io, or run any example locally:
cd examples/tanstack-ssr # or client-side, expo, next-ssr, react-router-ssr
pnpm install
pnpm dev| Example | Live | Local |
|---|---|---|
| Client-Side (Vite SPA) | shopify-cart.trackit.io/client | examples/client-side |
| Expo (React Native) | shopify-cart.trackit.io/expo | examples/expo |
| Next.js (App Router) | shopify-cart.trackit.io/next | examples/next-ssr |
| React Router v7 (SSR) | shopify-cart.trackit.io/react-router | examples/react-router-ssr |
| TanStack Start (SSR) | shopify-cart.trackit.io/tanstack | examples/tanstack-ssr |
The demo store has pre-configured discounts and gift cards you can test in any example. Per-line discounts show up directly on each cart line — original price struck through, discounted price, and the coupon name with its impact.
Automatic Discounts (applied automatically when conditions are met):
| Discount | Condition |
|---|---|
| Wax discount | Add any wax product to the cart |
| Buy 2 videographer board get 1 free | Add 3 videographer boards |
Discount Codes (enter in the discount code input):
| Code | Effect |
|---|---|
TRACKIT25 |
25% off the entire order |
COMPLETE10 |
10% off The Complete Snowboard |
Gift Cards (enter in the same input — auto-detected):
| Code | Value |
|---|---|
trackitgift15 |
$15 gift card |
trackitgift30 |
$30 gift card |
Gift cards stack — apply both for $45 off. They combine with discount codes too.
Getting Started
Framework Guides
Reference
- Framework agnostic — Works with Next.js, React Router, TanStack Start, Expo, Vite, or plain React
- Optimistic updates — Immediate UI feedback with automatic rollback on errors
- Debounced operations — Batches rapid changes to reduce API calls
- Action queue — Sequential cart operations prevent race conditions
- Complete Storefront Cart API — Full implementation of every Shopify Storefront Cart query and mutation
- Per-line discount allocations — Each cart line exposes its own discounts (title, type, savings amount)
- Discount codes — Apply and remove discount codes with validation feedback
- Gift cards — Add, remove, and stack multiple gift cards on a single cart
- Shipping — Shipping line details with original/discounted prices and discount breakdowns
- Delivery options — Select shipping methods from available delivery options per delivery group
- Cart attributes — Add custom key-value attributes to the cart
- Metafields — Set and delete cart metafields for extended cart data
- Buyer identity — Update customer info, delivery address, and country for accurate pricing
- Controlled cart identity — Persist cart IDs with your own storage strategy
- TypeScript — Full type safety with exported types
- Tree-shakeable — ESM with
sideEffects: false
The library supports two integration patterns depending on your framework.
For SPAs and mobile apps, call the Shopify Storefront API directly — no backend needed:
graph LR
subgraph Browser / App
CP2[CartProvider] --> UC2[useCart] --> CAH2[createCartActionHandler]
end
CAH2 -->|GraphQL| SHOP2[Shopify Storefront API]
Use createCartActionHandler({ storeDomain, storefrontAccessToken }) and pass it to CartProvider.
Server Actions are called directly from the client — no HTTP endpoints needed:
graph LR
subgraph Browser
CP1[CartProvider] --> UC1[useCart]
end
UC1 -->|Server Action| CA[cartAction]
subgraph Server
CA --> CAH1[createCartActionHandler]
end
CAH1 -->|GraphQL| SHOP1[Shopify Storefront API]
The onAction prop receives your Server Action directly. No createHttpCartAction needed.
When a user interacts with the cart:
- Immediate feedback - UI updates instantly with pending state (
line.isPending) - Debouncing - Rapid changes are batched (default 300ms, configurable)
- Queue processing - Operations execute sequentially to prevent conflicts
- Reconciliation - Server response merges with any new pending operations
- Error recovery - Failed operations are rolled back automatically
Direct Pattern (no backend needed) — calls Shopify Storefront API directly:
- Client-Side — Vite, CRA, or any browser SPA
- Expo — React Native with AsyncStorage
SSR Pattern (Server Actions) — no HTTP endpoints needed:
- Next.js — App Router with Server Actions
- React Router — v7 with SSR loaders
- TanStack Start — Server functions
The following helpers are used across multiple framework guides. Define them once in your project and import where needed.
SSR frameworks store the cart ID in cookies so it's accessible on both server and client:
function getCookie(name: string): string | null {
const match = document.cookie.match(new RegExp(`(?:^|; )${name}=([^;]*)`));
return match?.[1] ?? null;
}
function setCookie(name: string, value: string | null) {
if (value === null) {
document.cookie = `${name}=; path=/; max-age=0`;
} else {
document.cookie = `${name}=${value}; path=/; max-age=${60 * 60 * 24 * 30}; samesite=lax`;
}
}CartProvider is storage-agnostic. Control cartId and onCartIdChange with whatever storage fits your app:
- SSR web apps: cookies (see helpers above)
- Browser SPA:
localStorage - React Native/Expo:
AsyncStorage
Direct Pattern — no backend, localStorage persistence, auto-loads cart on refresh
// src/api.ts
import { createStorefrontClient } from "@trackit.io/shopify-cart";
import { createCartActionHandler } from "@trackit.io/shopify-cart/server";
const storeDomain = import.meta.env.VITE_SHOPIFY_STORE_DOMAIN;
const storefrontAccessToken = import.meta.env.VITE_SHOPIFY_STOREFRONT_ACCESS_TOKEN;
export const storefrontClient = createStorefrontClient({ storeDomain, storefrontAccessToken });
export const cartAction = createCartActionHandler({ storeDomain, storefrontAccessToken });import { useState, useCallback } from "react";
import { CartProvider } from "@trackit.io/shopify-cart/react";
import { cartAction } from "./api";
const CART_STORAGE_KEY = "shopify-cart-id";
function App() {
const [cartId, setCartId] = useState<string | null>(() => localStorage.getItem(CART_STORAGE_KEY));
const handleCartIdChange = useCallback((id: string | null) => {
setCartId(id);
if (id === null) {
localStorage.removeItem(CART_STORAGE_KEY);
} else {
localStorage.setItem(CART_STORAGE_KEY, id);
}
}, []);
return (
<CartProvider cartId={cartId} onCartIdChange={handleCartIdChange} onAction={cartAction}>
<Shop />
</CartProvider>
);
}The cart auto-loads when cartId is set but no initialCart is provided. Use isLoading from useCart() to show a loading state.
See Using the Cart for the useCart hook API.
Direct Pattern — no backend, AsyncStorage persistence, auto-loads cart on restart
import { createStorefrontClient } from "@trackit.io/shopify-cart";
import { createCartActionHandler } from "@trackit.io/shopify-cart/server";
const storeDomain = process.env.EXPO_PUBLIC_SHOPIFY_STORE_DOMAIN;
const storefrontAccessToken = process.env.EXPO_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN;
const storefrontClient = createStorefrontClient({ storeDomain, storefrontAccessToken });
const cartAction = createCartActionHandler({ storeDomain, storefrontAccessToken });import { useState, useEffect, useCallback } from "react";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { CartProvider } from "@trackit.io/shopify-cart/react";
const CART_STORAGE_KEY = "shopify-cart-id";
export default function App() {
const [cartId, setCartId] = useState(null);
useEffect(() => {
AsyncStorage.getItem(CART_STORAGE_KEY).then(setCartId);
}, []);
const handleCartIdChange = useCallback(async (id) => {
setCartId(id);
if (id) await AsyncStorage.setItem(CART_STORAGE_KEY, id);
else await AsyncStorage.removeItem(CART_STORAGE_KEY);
}, []);
return (
<CartProvider cartId={cartId} onCartIdChange={handleCartIdChange} onAction={cartAction}>
<Shop />
</CartProvider>
);
}The cart auto-loads when cartId is set but no initialCart is provided. Use isLoading from useCart() to show a loading state.
See Using the Cart for the useCart hook API.
See the examples/expo directory for a complete working example.
SSR Pattern — Server Actions, cookie persistence, SSR cart preloading
// app/actions.ts
"use server";
import {
createCartActionHandler,
type ProviderCartAction,
} from "@trackit.io/shopify-cart/server";
export const cartActionHandler = createCartActionHandler({
storeDomain: process.env.SHOPIFY_STORE_DOMAIN!,
storefrontAccessToken: process.env.SHOPIFY_STOREFRONT_ACCESS_TOKEN!,
});
export async function cartAction(action: ProviderCartAction) {
return cartActionHandler(action);
}Use the cookie helpers from Common Patterns:
// app/providers.tsx
"use client";
import { useCallback, useState } from "react";
import { CartProvider, type CartState } from "@trackit.io/shopify-cart/react";
import { cartAction } from "./actions";
const CART_COOKIE_KEY = "shopify-cart-id";
export function Providers({
children,
initialCart,
}: {
children: React.ReactNode;
initialCart: CartState | null;
}) {
const [cartId, setCartId] = useState<string | null>(
() => initialCart?.id ?? getCookie(CART_COOKIE_KEY),
);
const handleCartIdChange = useCallback((id: string | null) => {
setCartId(id);
setCookie(CART_COOKIE_KEY, id);
}, []);
return (
<CartProvider
cartId={cartId}
onCartIdChange={handleCartIdChange}
onAction={cartAction}
initialCart={initialCart}
>
{children}
</CartProvider>
);
}Load the cart on the server to avoid a flash of empty cart on hydration:
// app/layout.tsx
import { cookies } from "next/headers";
import { loadCart } from "@trackit.io/shopify-cart/server";
import { cartActionHandler } from "./actions";
import { Providers } from "./providers";
export default async function RootLayout({ children }) {
const cookieStore = await cookies();
const cartId = cookieStore.get("shopify-cart-id")?.value;
const cart = await loadCart(cartActionHandler, cartId);
return (
<html>
<body>
<Providers initialCart={cart}>{children}</Providers>
</body>
</html>
);
}See Using the Cart for the useCart hook API.
SSR Pattern — loaders, cookie persistence, SSR cart preloading
// app/routes/api.cart.ts
import {
createCartRequestHandler,
createCartActionHandler,
} from "@trackit.io/shopify-cart/server";
export const cartActionHandler = createCartActionHandler({
storeDomain: process.env.SHOPIFY_STORE_DOMAIN!,
storefrontAccessToken: process.env.SHOPIFY_STOREFRONT_ACCESS_TOKEN!,
});
const cartRequestHandler = createCartRequestHandler(cartActionHandler);
export async function action({ request }: { request: Request }) {
return cartRequestHandler(request);
}Use the cookie helpers from Common Patterns:
// app/root.tsx
import { Outlet, useLoaderData, type LoaderFunctionArgs } from "react-router";
import { useCallback, useState } from "react";
import { loadCart } from "@trackit.io/shopify-cart/server";
import { createHttpCartAction } from "@trackit.io/shopify-cart/client";
import { CartProvider } from "@trackit.io/shopify-cart/react";
import { cartActionHandler } from "./routes/api.cart";
const CART_COOKIE_KEY = "shopify-cart-id";
const cartAction = createHttpCartAction("/api/cart");
export async function loader({ request }: LoaderFunctionArgs) {
const cookieHeader = request.headers.get("Cookie") ?? "";
const cartId = cookieHeader.match(/shopify-cart-id=([^;]+)/)?.[1];
const cart = await loadCart(cartActionHandler, cartId);
return { cart };
}
export default function App() {
const { cart } = useLoaderData<typeof loader>();
const [cartId, setCartId] = useState<string | null>(() => cart?.id ?? getCookie(CART_COOKIE_KEY));
const handleCartIdChange = useCallback((id: string | null) => {
setCartId(id);
setCookie(CART_COOKIE_KEY, id);
}, []);
return (
<CartProvider
onAction={cartAction}
cartId={cartId}
onCartIdChange={handleCartIdChange}
initialCart={cart}
>
<Outlet />
</CartProvider>
);
}See Using the Cart for the useCart hook API.
SSR Pattern — server functions, cookie persistence, SSR cart preloading
TanStack Start requires using getCookie inside a server function:
// src/cart-actions.ts
import { createServerFn } from "@tanstack/react-start";
import { getCookie } from "@tanstack/react-start/server";
import {
createCartActionHandler,
loadCart,
} from "@trackit.io/shopify-cart/server";
export const cartActionHandler = createCartActionHandler({
storeDomain: import.meta.env.VITE_SHOPIFY_STORE_DOMAIN!,
storefrontAccessToken: import.meta.env.VITE_SHOPIFY_STOREFRONT_ACCESS_TOKEN!,
});
export const getInitialCart = createServerFn({ method: "GET" }).handler(
async () => {
const cartId = getCookie("shopify-cart-id");
return loadCart(cartActionHandler, cartId);
},
);Use the cookie helpers from Common Patterns:
// src/routes/__root.tsx
import { createRootRoute } from "@tanstack/react-router";
import { useCallback, useState } from "react";
import { CartProvider } from "@trackit.io/shopify-cart/react";
import { getInitialCart, cartActionServer } from "../cart-actions";
const CART_COOKIE_KEY = "shopify-cart-id";
export const Route = createRootRoute({
loader: async () => {
const cart = await getInitialCart();
return { cart };
},
component: RootComponent,
});
function RootComponent() {
const { cart } = Route.useLoaderData();
const [cartId, setCartId] = useState<string | null>(() => cart?.id ?? getCookie(CART_COOKIE_KEY));
const handleCartIdChange = useCallback((id: string | null) => {
setCartId(id);
setCookie(CART_COOKIE_KEY, id);
}, []);
return (
<CartProvider
onAction={(action) => cartActionServer({ data: action })}
cartId={cartId}
onCartIdChange={handleCartIdChange}
initialCart={cart}
>
<Outlet />
</CartProvider>
);
}See Using the Cart for the useCart hook API.
Once your provider is set up, use the useCart hook in any component:
import { useCart } from "@trackit.io/shopify-cart/react";
function Shop() {
const {
cart,
isLoading,
isSyncing,
addLine,
updateLineQuantity,
removeLine,
applyCode,
removeCode,
clearAllCodes,
updateBuyerIdentity,
} = useCart();
if (isLoading) return <span>Loading cart...</span>;
const handleAddToCart = (variantId: string, product: ProductInfo) => {
addLine(variantId, {
title: product.title,
variantTitle: product.variantTitle,
price: product.price,
image: product.image,
});
};
return (
<div>
<p>Items: {cart.totalQuantity}</p>
<p>
Subtotal: {cart.subtotal.amount} {cart.subtotal.currencyCode}
</p>
<p>
Total: {cart.total.amount} {cart.total.currencyCode}
</p>
{isSyncing && <span>Syncing...</span>}
{/* Cart lines */}
{cart.lines.map((line) => (
<div key={line.id} style={{ opacity: line.isPending ? 0.7 : 1 }}>
<span>
{line.title} x {line.quantity}
</span>
<button
onClick={() => updateLineQuantity(line.id, line.quantity + 1)}
>
+
</button>
<button
onClick={() => updateLineQuantity(line.id, line.quantity - 1)}
>
-
</button>
<button onClick={() => removeLine(line.id)}>Remove</button>
</div>
))}
{/* Discount codes */}
{cart.discountCodes.map((dc) => (
<div key={dc.code}>
{dc.code} {dc.applicable ? "(applied)" : "(not applicable)"}
<button onClick={() => removeCode(dc.code, "discount")}>
Remove
</button>
</div>
))}
{/* Gift cards */}
{cart.appliedGiftCards?.map((gc) => (
<div key={gc.id}>
Gift card ****{gc.lastCharacters} (-{gc.amountUsed.amount}{" "}
{gc.amountUsed.currencyCode})
<button onClick={() => removeCode(gc.id, "giftCard")}>Remove</button>
</div>
))}
{/* Shipping */}
{cart.shipping && (
<div>
Shipping: {cart.shipping.title} —{" "}
{cart.shipping.discountedPrice.amount}{" "}
{cart.shipping.discountedPrice.currencyCode}
</div>
)}
{/* Delivery options */}
{cart.deliveryGroups.map((group) => (
<div key={group.id}>
<h3>Delivery Group</h3>
<p>
Selected: {group.selectedDeliveryOption?.title ?? "None"}
</p>
{group.deliveryOptions.map((option) => (
<button
key={option.handle}
onClick={() =>
updateSelectedDeliveryOptions([
{
deliveryGroupId: group.id,
deliveryOptionHandle: option.handle,
},
])
}
>
{option.title} — {option.estimatedCost.amount}{" "}
{option.estimatedCost.currencyCode}
</button>
))}
</div>
))}
{/* Apply a code (auto-detects discount vs gift card) */}
<button onClick={() => applyCode("SUMMER20")}>Apply Code</button>
<button onClick={() => clearAllCodes()}>Clear All Codes</button>
{/* Update buyer identity for accurate shipping/tax */}
<button
onClick={() =>
updateBuyerIdentity({
email: "customer@example.com",
deliveryAddress: {
address1: "123 Main St",
city: "Ottawa",
province: "ON",
country: "CA",
zip: "K1A 0A6",
},
})
}
>
Set Address
</button>
{/* Cart attributes */}
<button onClick={() => setAttribute("gift-wrap", "true")}>
Add Gift Wrap
</button>
<button onClick={() => setAttribute("gift-message", "Happy Birthday!")}>
Set Gift Message
</button>
<button onClick={() => removeAttribute("gift-wrap")}>
Remove Gift Wrap
</button>
<p>Attributes: {cart.attributes.map((a) => `${a.key}=${a.value}`).join(", ")}</p>
{/* Metafields */}
<button
onClick={() =>
setMetafields([
{
ownerId: cart.id!,
key: "custom.special_instructions",
value: "Leave at door",
type: "single_line_text_field",
},
])
}
>
Set Metafield
</button>
</div>
);
}Once the cart is ready, use cart.checkoutUrl to send the user to Shopify's checkout. This library manages the cart — the checkout navigation is up to you.
Redirect or open the checkout URL in the current tab:
const { cart } = useCart();
<button
disabled={!cart.checkoutUrl || cart.lines.length === 0}
onClick={() => window.location.href = cart.checkoutUrl!}
>
Checkout
</button>For the best mobile experience, use Shopify's native checkout sheet via @shopify/checkout-sheet-kit. It opens Shopify checkout as an in-app modal with native UI:
pnpm add @shopify/checkout-sheet-kitimport { useShopifyCheckoutSheet } from "@shopify/checkout-sheet-kit";
function CheckoutButton() {
const { cart } = useCart();
const shopifyCheckout = useShopifyCheckoutSheet();
return (
<Button
title="Checkout"
disabled={!cart.checkoutUrl || cart.lines.length === 0}
onPress={() => shopifyCheckout.present(cart.checkoutUrl!)}
/>
);
}You can also preload the checkout for faster open:
useEffect(() => {
if (cart.checkoutUrl) {
shopifyCheckout.preload(cart.checkoutUrl);
}
}, [cart.checkoutUrl]);If @shopify/checkout-sheet-kit is not available (e.g. Expo Go), fall back to opening the URL in the browser:
import * as Linking from "expo-linking";
Linking.openURL(cart.checkoutUrl!);You can provide a custom fetch function to use an alternative HTTP client like axios. This is useful for adding interceptors, logging, or reusing an existing axios instance with configured timeouts and headers.
import axios from "axios";
import type { FetchFn } from "@trackit.io/shopify-cart/server";
const axiosFetch: FetchFn = async (input, init) => {
const url = typeof input === "string" ? input : input.url;
const response = await axios({
url,
method: (init?.method as string) ?? "POST",
headers: init?.headers as Record<string, string>,
data: init?.body,
});
return new Response(JSON.stringify(response.data), {
status: response.status,
headers: response.headers as HeadersInit,
});
};Use with createStorefrontClient:
const client = createStorefrontClient({
storeDomain: "your-store.myshopify.com",
storefrontAccessToken: "your-token",
fetch: axiosFetch,
});Or with createCartActionHandler:
const cartActionHandler = createCartActionHandler({
storeDomain: "your-store.myshopify.com",
storefrontAccessToken: "your-token",
fetch: axiosFetch,
});Or with createHttpCartAction:
const cartAction = createHttpCartAction({
endpoint: "/api/cart",
fetch: axiosFetch,
});# Install dependencies
pnpm install
# Start dev server (watch mode — rebuilds on changes)
pnpm run dev
# Build for production
pnpm run buildWe welcome issues and pull requests! Whether you're fixing bugs, adding features, or improving documentation:
- Open an issue to discuss ideas or report bugs before starting work
- Fork and create a branch following our conventions (
feat/,fix/,docs/, etc.) - Run tests locally before submitting:
pnpm test # Run tests in watch mode pnpm lint:fix # Auto-fix linting issues
- Submit a PR with a clear description of changes
See the examples to test your changes across different frameworks (Next.js, React Router, Expo, etc.).
Creates an action handler that sends cart operations to your API endpoint.
import { createHttpCartAction } from "@trackit.io/shopify-cart/client";
const cartAction = createHttpCartAction("/api/cart");
// or with headers
const cartAction = createHttpCartAction("/api/cart", {
"X-Custom-Header": "value",
});
// or with options object (supports custom fetch)
const cartAction = createHttpCartAction({
endpoint: "/api/cart",
headers: { "X-Custom-Header": "value" },
fetch: customFetch, // optional: custom fetch implementation
});Creates a handler function for cart operations. This is the core handler that can be used directly (Server Actions) or wrapped with createCartRequestHandler for HTTP endpoints.
import { createCartActionHandler } from "@trackit.io/shopify-cart/server";
const cartActionHandler = createCartActionHandler({
storeDomain: "your-store.myshopify.com",
storefrontAccessToken: "your-token",
});
// Use directly
const cart = await cartActionHandler({
type: "load",
cartId: "gid://shopify/Cart/123",
});Creates a Fetch API compatible HTTP handler that wraps a cart action handler.
import {
createCartRequestHandler,
createCartActionHandler,
} from "@trackit.io/shopify-cart/server";
const cartActionHandler = createCartActionHandler({
storeDomain: "your-store.myshopify.com",
storefrontAccessToken: "your-token",
});
const cartRequestHandler = createCartRequestHandler(cartActionHandler, {
cors: { origin: ["https://example.com"] }, // optional
});Helper for SSR cart preloading. Loads a cart by ID with graceful error handling.
import { loadCart, createCartActionHandler } from "@trackit.io/shopify-cart/server";
const cartActionHandler = createCartActionHandler({ ... });
// Returns CartState or null (if cart not found or expired)
const cart = await loadCart(cartActionHandler, cartId);Creates a typed GraphQL client for the Shopify Storefront API.
import { createStorefrontClient } from "@trackit.io/shopify-cart/server";
const client = createStorefrontClient({
storeDomain: "your-store.myshopify.com",
storefrontAccessToken: "your-token",
});
const result = await client.GetProducts({ first: 10 });Context provider that manages cart state.
import { CartProvider } from "@trackit.io/shopify-cart/react";
<CartProvider
onAction={cartAction} // Required: handles cart operations
cartId={cartId} // Optional: controlled cart ID
onCartIdChange={setCartId} // Optional: cart ID change callback
initialCart={cart} // Optional: SSR initial state
onError={handleError} // Optional: error callback
debounceMs={300} // Optional: debounce delay (default: 300)
>
{children}
</CartProvider>;Main hook for cart state and operations.
const {
cart, // CartUIState - current cart state
isLoading, // boolean - true while auto-loading an existing cart on mount
isSyncing, // boolean - true when syncing with server
pendingCount, // number - count of pending operations
invalidCodeError, // string | null - error message when a code fails to apply
addLine, // (merchandiseId: string, productInfo: ProductInfo) => void
updateLineQuantity, // (lineId: string, quantity: number) => void
removeLine, // (lineId: string) => void
updateNote, // (note: string) => void
applyCode, // (code: string) => void — tries discount first, then gift card
removeCode, // (id: string, type: "discount" | "giftCard") => void
clearAllCodes, // () => void — removes all discount codes and gift cards
refreshCart, // () => Promise<void> — force refresh from server
clearCart, // () => Promise<void> — remove all lines from cart
updateBuyerIdentity, // (buyerIdentity: BuyerIdentityInput) => void
getAttributes, // () => CartAttribute[] — get all cart attributes
getAttribute, // (key: string) => string | undefined — get single attribute value
setAttribute, // (key: string, value: string) => void — set single attribute
removeAttribute, // (key: string) => void — remove single attribute
updateAttributes, // (attributes: CartAttribute[]) => void — replace all attributes
setMetafields, // (metafields: CartMetafieldInput[]) => void — set cart metafields
deleteMetafield, // (ownerId: string, key: string) => void — delete cart metafield
updateSelectedDeliveryOptions, // (options: SelectedDeliveryOptionInput[]) => void — select shipping methods
} = useCart();import {
useCartLines, // () => CartLine[]
useCartTotals, // () => { totalQuantity, subtotal }
useAddToCart, // () => addLine function
useUpdateLineQuantity, // () => updateLineQuantity function
useRemoveFromCart, // () => removeLine function
useUpdateNote, // () => updateNote function
useRefreshCart, // () => refreshCart function
useClearCart, // () => clearCart function
// Note: Additional convenience hooks for attributes, metafields, and delivery options
// can be created following the same pattern (useCart() + destructure specific methods)
} from "@trackit.io/shopify-cart/react";interface CartUIState {
id: string | null;
lines: CartLine[];
totalQuantity: number;
subtotal: Money;
total: Money;
shipping: ShippingLine | null;
checkoutUrl: string | null;
note: string | null;
discountCodes: DiscountCode[];
discountAllocations: DiscountAllocation[];
groupedDiscountAllocations: GroupedDiscountAllocation[];
appliedGiftCards?: AppliedGiftCard[];
attributes: CartAttribute[];
deliveryGroups: CartDeliveryGroup[];
}
interface CartLine {
id: string;
merchandiseId: string;
quantity: number;
title: string;
variantTitle?: string;
handle?: string;
price: Money; // discounted unit price (after cart-level discounts)
compareAtPrice?: Money; // variant's "compare at" price
originalPrice?: Money; // variant's base price (before discounts)
image?: { url: string; altText?: string };
discountAllocations?: DiscountAllocation[]; // per-line discounts (title, amount, type)
isPending?: boolean; // true for optimistic updates
}
interface Money {
amount: string;
currencyCode: string;
}
interface DiscountCode {
code: string;
applicable: boolean;
isPending?: boolean;
}
interface DiscountAllocation {
title: string;
amount: Money;
type?: "code" | "automatic";
targetType?: "LINE_ITEM" | "SHIPPING_LINE";
}
interface GroupedDiscountAllocation {
title: string;
totalAmount: Money;
type: "code" | "automatic";
count: number;
details: DiscountAllocation[];
targetType?: "LINE_ITEM" | "SHIPPING_LINE";
}
interface AppliedGiftCard {
id: string;
lastCharacters: string;
amountUsed: Money;
}
interface ShippingLine {
title: string;
originalPrice: Money;
discountedPrice: Money;
discounts?: ShippingDiscount[];
}
interface ShippingDiscount {
title: string;
amount: Money;
type: "code" | "automatic";
}
interface BuyerIdentityInput {
email?: string;
phone?: string;
countryCode?: string;
customerAccessToken?: string;
deliveryAddress?: DeliveryAddressInput;
}
interface DeliveryAddressInput {
firstName?: string;
lastName?: string;
address1?: string;
address2?: string;
city?: string;
province?: string;
zip?: string;
country?: string;
phone?: string;
}
interface CartAttribute {
key: string;
value: string;
}
interface CartMetafieldInput {
ownerId: string;
key: string;
value: string;
type: string;
}
interface SelectedDeliveryOptionInput {
deliveryGroupId: string;
deliveryOptionHandle: string;
}
interface CartDeliveryOption {
handle: string;
title: string;
estimatedCost: Money;
}
interface CartDeliveryGroup {
id: string;
selectedDeliveryOption: { handle: string; title: string } | null;
deliveryOptions: CartDeliveryOption[];
}
interface ProductInfo {
title: string;
variantTitle?: string;
handle?: string;
price: Money;
compareAtPrice?: Money;
image?: { url: string; altText?: string };
}
interface CartError {
code: CartErrorCode;
message: string;
cause?: unknown;
context?: {
cartId?: string | null;
lineId?: string;
merchandiseId?: string;
};
}
type CartErrorCode =
| "ADD_FAILED"
| "UPDATE_FAILED"
| "REMOVE_FAILED"
| "NOTE_UPDATE_FAILED"
| "DISCOUNT_UPDATE_FAILED"
| "BUYER_IDENTITY_UPDATE_FAILED"
| "LOAD_FAILED";
type ProviderCartAction =
| { type: "add"; cartId: string | null; lines: CartLineInput[] }
| { type: "update"; cartId: string; lines: CartLineUpdateInput[] }
| { type: "remove"; cartId: string; lineIds: string[] }
| { type: "load"; cartId: string }
| { type: "updateNote"; cartId: string; note: string }
| { type: "updateAttributes"; cartId: string; attributes: CartAttribute[] }
| { type: "setMetafields"; cartId: string; metafields: CartMetafieldInput[] }
| { type: "deleteMetafield"; cartId: string; ownerId: string; key: string }
| { type: "updateSelectedDeliveryOptions"; cartId: string; selectedDeliveryOptions: SelectedDeliveryOptionInput[] }
| { type: "updateDiscountCodes"; cartId: string; discountCodes: string[] }
| { type: "addGiftCardCodes"; cartId: string; giftCardCodes: string[] }
| { type: "updateGiftCardCodes"; cartId: string; giftCardCodes: string[] }
| { type: "removeGiftCardCodes"; cartId: string; appliedGiftCardIds: string[] }
| { type: "applyCode"; cartId: string; code: string }
| { type: "removeCode"; cartId: string; code: string; codeType: "discount" | "giftCard" }
| { type: "updateBuyerIdentity"; cartId: string; buyerIdentity: BuyerIdentityInput };Apache-2.0