Browser client & hooks
createStorekitClient() and the React hooks — useCart, useSession, useAddresses, usePaymentConfirmation.
Open in
@usestorekit/sdk/react is a "use client" module for the browser. It talks to
your same-origin proxy (storekit.handler()), so your store key never
reaches the client and the session rides along in an httpOnly cookie.
createStorekitClient()
"use client";
import { createStorekitClient } from "@usestorekit/sdk/react";
export const storefront = createStorekitClient();Prop
Type
It returns resource namespaces (same shape as the server client), cookie-backed
cart/auth facades, and the hooks below.
Hooks
useCart()
Backed by a shared store — every component using the same client stays in sync without polling. Updating the cart in one place updates the header badge everywhere.
const {
cart, // Cart | null
count, // number — total quantity
loading,
error, // StorefrontError | null
add, // (item, optimistic?) => Promise<Result<Cart>>
setQuantity, // (lineId, qty) => Promise<Result<Cart>>
remove, // (lineId) => Promise<Result<Cart>>
clear, // () => Promise<void>
refresh, // () => Promise<void>
} = storefront.useCart();"use client";
import { storefront } from "@/lib/storekit-client";
export function CartBadge() {
const { count } = storefront.useCart();
return <span>{count}</span>;
}Optimistic by default
Cart mutations apply immediately, then reconcile with the server's response (and roll back on error):
setQuantity/remove— the line and thesubtotalupdate instantly; thecountbadge too.clear— the cart empties at once (no round-trip flash).add— adding more of an item already in the cart bumps that line instantly; adding a brand-new item renders its full row instantly too (see below).
The grand total and the charges breakdown (tax, packaging, delivery) are
not guessed optimistically — they can hinge on thresholds (free delivery
over ₹X, tax tiers) we can't reproduce in the browser. They settle to the
server's authoritative value on reconcile, a moment after the subtotal moves.
Rapid taps are safe: a slow in-flight response can't overwrite newer state.
Optimistic first adds
To render a brand-new line, the client needs the variant's display data
(name, price, image) — which it can't derive from a variant id alone. It
harvests that data automatically from every catalog read it proxies
(products.list / products.get / search) into a small in-memory index, so a
first add just works with no extra arguments:
const { add } = storefront.useCart();
// variant was loaded via products.get / list / search → line renders instantly
await add({ variantId, quantity: 1 });For the cold case — a server-rendered product page where the browser never
fetched the catalog itself — pass an optimistic hint to guarantee a
synchronous first add. Build it from the product + variant you already have with
lineHint:
import { lineHint } from "@usestorekit/sdk/react";
await add({ variantId: variant.id, quantity: 1 }, lineHint(product, variant));
// fold modifier prices into the optimistic unit price:
await add(
{ variantId, quantity: 1, modifiers },
lineHint(product, variant, { modifiers: selected }),
);Without a hint and with a cold index (or before the cart has loaded), the
add falls back to a count-only badge bump and the row arrives when the server
responds — still no UI block, just no synthesized row in that one case.
useSession()
const {
data, // Customer | null
loading, // true until first fetch resolves
error,
refresh, // () => Promise<void>
update, // (input) => Promise<Result<Customer>> — patches profile + syncs state
} = storefront.useSession();update is optimistic — the name/email change shows immediately and rolls back
if the request fails.
useAddresses()
The signed-in customer's saved addresses, with optimistic list CRUD over one shared store. New rows appear, edits apply, and deletes drop instantly, then reconcile with the server (rolling back on error). Setting a default un-defaults the others right away.
const {
addresses, // CustomerAddress[] — [] until first fetch resolves
loading, // true until first fetch resolves
error,
refresh, // () => Promise<void>
create, // (input) => Promise<Result<CustomerAddress>>
update, // (addressId, input) => Promise<Result<CustomerAddress>>
remove, // (addressId) => Promise<Result<{ success }>>
} = storefront.useAddresses();"use client";
import { storefront } from "@/lib/storekit-client";
export function AddressBook() {
const { addresses, create, remove } = storefront.useAddresses();
return (
<ul>
{addresses.map((a) => (
<li key={a.id}>
{a.address}, {a.city} {a.isDefault && "★"}
<button onClick={() => remove(a.id)}>Delete</button>
</li>
))}
</ul>
);
}See Customers → addresses for the input fields.
useStore()
const { data, loading, error } = storefront.useStore(); // ResourceState<Store>usePaymentConfirmation()
Drives the payment return page — confirms the order, retries transient network failures, and refreshes the cart on success.
const { status, order, message, error, retry } =
storefront.usePaymentConfirmation(orderId);status is "processing" | "success" | "pending" | "failed" | "error". See
Payments for the full example.
Cart & auth facades (without hooks)
Need imperative calls outside a component? The same operations are available directly:
await storefront.cart.add({ variantId, quantity: 1 });
await storefront.cart.setQuantity(lineId, 2);
await storefront.cart.remove(lineId);
await storefront.cart.clear();
await storefront.auth.requestOtp(phone);
await storefront.auth.verifyOtp(phone, otp);
await storefront.auth.session();
await storefront.auth.logout();Prefer the hooks in UI so shared state stays consistent.
lineHint()
Builds the optimistic display data for useCart().add from a catalog
product + variant you already have, so you don't hand-map fields (the mapping
lives in one place and tracks the catalog types). Only needed for the cold
first-add case — see Optimistic first adds.
import { lineHint } from "@usestorekit/sdk/react";
lineHint(product, variant);
lineHint(product, variant, { modifiers: selected }); // folds modifier prices into the unit priceIt maps variant.name / sku / price / attributes and product.name / slug / images[0] / isAvailable into an OptimisticLineHint. Pass the selected
modifiers so their prices are added to the optimistic unit price — otherwise
the subtotal briefly under-counts until the server reconciles.
formatMoney()
Exported from all three entry points. Locale-pinned by default so server and client render the same string (no hydration mismatch):
import { formatMoney } from "@usestorekit/sdk/react";
formatMoney("1200.00"); // "₹1,200.00"
formatMoney(1200, "USD", "en-US"); // "$1,200.00"Escape hatch
storefront.$core is the underlying framework-agnostic
core client if you need a method the facade doesn't
surface.