StoreKitdocs
SDK

Browser client & hooks

createStorekitClient() and the React hooks — useCart, useSession, useAddresses, usePaymentConfirmation.

Open in
GitHub

@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()

lib/storekit-client.ts
"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();
components/cart-badge.tsx
"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 the subtotal update instantly; the count badge 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();
components/address-book.tsx
"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 price

It 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.

On this page