Skip to content
Kitenzo Headless is invite-only. To enable it on your store, email support@kitenzo.com.

Hooks

All hooks require a <KitenzoProvider> ancestor (except where noted). Provider hooks useKitenzo and useSettings are documented on the Provider & client page.

Fetch the list of published bundles on mount.

const { bundles, isLoading, error, refetch } = useBundles();
FieldTypeDescription
bundlesBundle[]Published bundles (no product data).
isLoadingbooleanInitial load state.
errorError | nullFetch error, if any.
refetch() => voidRe-run the request.

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 });
ParamTypeDescription
idnumberBundle ID.
options.initialDataBundleDetailPre-fetched data (e.g. from a server loader). Skips the initial client fetch.

Returns { bundle: BundleDetail \| null, isLoading, error, refetch }.

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):

FieldTypeDescription
selectionsSectionSelectionsRecord<sectionId, BundleSelection[]>.
currentSectionIndexnumberActive section (0-based).
currentSectionBundleSection | nullActive section object.
isSectionValidbooleanCurrent section meets its constraints.
isValidbooleanAll sections meet their constraints.
isCompletebooleanLegacy completeness flag — see the aside below.
isSatisfiedbooleanThe bundle’s actual limit rules are met — a selection /configure will accept. Gate add-to-cart on this.
allItemsBundleSelection[]Flat list of all selections.
errorsValidationError[]Limit-rule / required-product violations (empty when valid).
conditionsConditionsSnapshotConditions-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"
FieldTypeDescription
originalPricestring | nullRaw, e.g. "45.00".
discountedPricestring | nullRaw, e.g. "40.50".
formattedOriginalPricestring | nulle.g. "$45.00".
formattedDiscountedPricestring | nulle.g. "$40.50".
discountTypestring | null'percentage' | 'fixed' | 'price' | null.
discountValuestring | nullDiscount amount.
currencystring | nullCurrency label.
hasDiscountbooleanWhether 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.

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 }

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):

  • phase is 'idle' | 'configuring' | 'adding' | 'attributes' | 'added' | 'failed'. The adding/attributes split matters: the discount lives in a second mutation, so only added means “safe to check out”.
  • addToCart never rejects — it resolves { ok: true, result } or { ok: false, reason, error }, with failureReason one of already-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.

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.