Hooks
All hooks require a <KitenzoProvider> ancestor (except where noted). Provider hooks useKitenzo and useSettings are documented on the Provider & client page.
useBundles()
Section titled “useBundles()”Fetch the list of published bundles on mount.
const { bundles, isLoading, error, refetch } = useBundles();| Field | Type | Description |
|---|---|---|
bundles | Bundle[] | Published bundles (no product data). |
isLoading | boolean | Initial load state. |
error | Error | null | Fetch error, if any. |
refetch | () => void | Re-run the request. |
useBundle(id, options?)
Section titled “useBundle(id, options?)”Fetch a single bundle’s full detail (sections + products + variants). Supports SSR hydration. When countryCode is set on the provider, the bundle loads with Shopify Markets presentment prices and market availability.
const { bundle, isLoading, error, refetch } = useBundle(42, { initialData });| Param | Type | Description |
|---|---|---|
id | number | Bundle ID. |
options.initialData | BundleDetail | Pre-fetched data (e.g. from a server loader). Skips the initial client fetch. |
Returns { bundle: BundleDetail \| null, isLoading, error, refetch }.
useBundleBuilder(bundle)
Section titled “useBundleBuilder(bundle)”Wraps createBundleBuilder with React state. Tracks the customer’s selection, section navigation, validity and the conditions-engine result.
const b = useBundleBuilder(bundle);
b.addItem(sectionId, variantId, 1);b.updateQuantity(sectionId, variantId, 3);b.removeItem(sectionId, variantId);b.nextSection();State (read):
| Field | Type | Description |
|---|---|---|
selections | SectionSelections | Record<sectionId, BundleSelection[]>. |
currentSectionIndex | number | Active section (0-based). |
currentSection | BundleSection | null | Active section object. |
isSectionValid | boolean | Current section meets its constraints. |
isValid | boolean | All sections meet their constraints. |
isComplete | boolean | Legacy completeness flag — see the aside below. |
isSatisfied | boolean | The bundle’s actual limit rules are met — a selection /configure will accept. Gate add-to-cart on this. |
allItems | BundleSelection[] | Flat list of all selections. |
errors | ValidationError[] | Limit-rule / required-product violations (empty when valid). |
conditions | ConditionsSnapshot | Conditions-engine result: hiddenSectionIds, hiddenProducts, hideCartButton, gotoSectionId, discountOverride. All-empty when the bundle has no rule graph. |
Methods (write): addItem(sectionId, variantId, quantity?), removeItem(sectionId, variantId), updateQuantity(sectionId, variantId, quantity), reset(), nextSection(), prevSection(), goToSection(index), getSectionQuantity(sectionId).
useBundlePrice(bundle, selections, options?)
Section titled “useBundlePrice(bundle, selections, options?)”Compute the price locally (no API call). Recomputes instantly as selections change, applies any firing conditions-engine discount, and formats with the shop’s money format.
const price = useBundlePrice(bundle, builder.selections);// price.formattedDiscountedPrice → "$40.50"| Field | Type | Description |
|---|---|---|
originalPrice | string | null | Raw, e.g. "45.00". |
discountedPrice | string | null | Raw, e.g. "40.50". |
formattedOriginalPrice | string | null | e.g. "$45.00". |
formattedDiscountedPrice | string | null | e.g. "$40.50". |
discountType | string | null | 'percentage' | 'fixed' | 'price' | null. |
discountValue | string | null | Discount amount. |
currency | string | null | Currency label. |
hasDiscount | boolean | Whether a discount applies. |
options.currency overrides the shop currency. All fields are null when there are no selections. With countryCode on the provider, prices come back in the shopper’s market currency, formatted with Intl.NumberFormat instead of moneyFormat — no code change needed.
useBundleCart()
Section titled “useBundleCart()”Submit a selection via client.submitBundle, tracking loading/error state. It does not touch a cart — for the whole flow, prefer useBundleAjaxCart (themed store) or useStorefrontBundleCart (Hydrogen) below.
const { addBundleToCart, isLoading, error, lastResult } = useBundleCart();
const result = await addBundleToCart(bundle, builder.selections, { countryCode: 'US' });// result: SubmitBundleResult { configuredBundleId, variantId, productId, discount, subscriptionId, pricing }useBundleAjaxCart(options?)
Section titled “useBundleAjaxCart(options?)”Configures the bundle and puts it in the theme’s own cart (/cart/add.js → /cart/update.js), for a widget embedded in a normal Shopify storefront. Merges _bundles rather than replacing it, turns non-2xx responses into real errors, and never double-adds on retry.
const { addToCart, isAdding, isAdded, error, phase, failureReason, reset } = useBundleAjaxCart({ onAdded: () => { window.location.href = '/cart'; },});
<button disabled={isAdding} onClick={() => addToCart(bundle, selections)}> {isAdding ? 'Adding…' : 'Add to cart'}</button>Key facts about the shared cart-flow state (UseBundleCartFlowResult):
phaseis'idle' | 'configuring' | 'adding' | 'attributes' | 'added' | 'failed'. Theadding/attributessplit matters: the discount lives in a second mutation, so onlyaddedmeans “safe to check out”.addToCartnever rejects — it resolves{ ok: true, result }or{ ok: false, reason, error }, withfailureReasonone ofalready-in-progress,cart-busy,cart-not-ready,configure-failed,cart-error,timeout.- Options:
onAdded(result),onError(error, reason),routePrefix(locale-scoped stores, e.g.'/en-gb'),fetchImpl.
For a Storefront-API cart, useStorefrontBundleCart from @kitenzo/react/hydrogen returns the identical shape (plus hasMissingItems and a timeoutMs option), so one set of buttons serves both cart modes.
useSellingPlan(bundle, options?)
Section titled “useSellingPlan(bundle, options?)”Purchase-option state for Kitenzo Subscriptions (subscriptions beta). Plans come off bundle.sellingPlans; one-time purchase is the null selection.
const { plans, hasPlans, selectedPlanId, selectPlan, cartLineOptions } = useSellingPlan(bundle);
// render one-time + each plan as radios; then pass cartLineOptions on:const payload = buildCartPayload(result, cart.attributes, { bundle, selections }, cartLineOptions);A stale selection (a plan unpublished mid-session) falls back to one-time rather than sending a dead plan id to the cart.