# Hooks: useVariantPicker to useBundleRecommendations (/themes/sdk/reference/product/hooks-1)

```ts
import { … } from '@poltura/theme-sdk/product';
```

## `useVariantPicker` [#usevariantpicker]

The variant picker of the nearest product: its options, the choice and the variant it picks.

```ts
useVariantPicker(): VariantPickerState
```

The choice is host-owned and shared by every block of the product; `select` also emits the variant-selected event.

**Returns:** The picker's options, the choice and the picked variant.

**Example**

```tsx
import { VariantOption, VariantOptionValue, useVariantPicker } from '@poltura/theme-sdk/product';

export function Picker() {
  const picker = useVariantPicker();
  return (
    <>
      {picker.options.map((option) => (
        <VariantOption key={option.id} option={option}>
          {option.values.map((value) => (
            <VariantOptionValue
              key={value.id}
              value={value}
              onSelect={() => picker.select(option.id, value.id)}
            />
          ))}
        </VariantOption>
      ))}
    </>
  );
}
```

## `useProductGallery` [#useproductgallery]

The gallery state of the nearest product, which moves to the picked variant's first image.

```ts
useProductGallery(): ProductGalleryState
```

**Returns:** The images, the one shown and the zoomed view.

**Example**

```tsx
import { MediaImage } from '@poltura/theme-sdk/media';
import { useProductGallery } from '@poltura/theme-sdk/product';

export function Gallery() {
  const gallery = useProductGallery();
  const image = gallery.images[gallery.active];
  return image ? (
    <button type='button' onClick={gallery.next}>
      <MediaImage image={image} role='pdp' />
    </button>
  ) : null;
}
```

## `useBundleSelection` [#usebundleselection]

The bundle offered with the product page's product and the shopper's choice in it.

```ts
useBundleSelection(): BundleSelectionState | null
```

Pricing is host-owned: the choice is quoted on the host and the bundle goes to the cart as one signed offer.

**Returns:** The bundle, its items and groups with the choice, or null when the product has no bundle offer.

**Example**

```tsx
import { useBundleSelection } from '@poltura/theme-sdk/product';

export function BundleItems() {
  const bundle = useBundleSelection();
  if (bundle === null) return null;
  return (
    <ul>
      {bundle.items.map((item) => (
        <li key={item.id}>
          <label>
            <input
              type='checkbox'
              checked={item.selected}
              disabled={!item.selectable && !item.selected}
              onChange={(event) => bundle.toggle(item.id, event.currentTarget.checked)}
            />
            {item.title}
          </label>
        </li>
      ))}
    </ul>
  );
}
```

## `useNotifyMe` [#usenotifyme]

The form state behind [`NotifyMe`](/themes/sdk/reference/product/defaults-2#notifyme), for a theme drawing its own notify-me form.

```ts
useNotifyMe(variantId: string | null): NotifyMeState
```

The request runs on the host behind the store's bot check; the notice is sent once, when the variant can be bought.

**Parameters**

- `variantId: string | null`: The variant to hear about, or null for none.

**Returns:** The address, the newsletter choice, the status and the submit action.

**Example**

```tsx
import { useNotifyMe } from '@poltura/theme-sdk/product';

export function Notify({ variantId }: { variantId: string }) {
  const notify = useNotifyMe(variantId);
  if (!notify.enabled) return null;
  if (notify.status === 'done') return <p>We will email you.</p>;
  return (
    <form onSubmit={(event) => { event.preventDefault(); void notify.submit(); }}>
      <input type='email' value={notify.email} onChange={(event) => notify.setEmail(event.currentTarget.value)} />
      <button type='submit' disabled={notify.status === 'submitting'}>Notify me</button>
    </form>
  );
}
```

## `useNearestProduct` [#usenearestproduct]

The product the code sits under: the product page's, a product card's or a [`ProductScope`](/themes/sdk/reference/product/parts-1#productscope)'s.

```ts
useNearestProduct(): Product | null
```

**Returns:** The nearest product, or null outside any product.

**Example**

```tsx
import { useNearestProduct } from '@poltura/theme-sdk/product';

export function Vendor() {
  const product = useNearestProduct();
  return product?.vendor ? <p>{product.vendor}</p> : null;
}
```

## `useProductBuy` [#useproductbuy]

The buy box state of the nearest product, shared by every part of the box.

```ts
useProductBuy(): ProductBuy
```

Throws outside a product: place it under the product page, a product card or a [`ProductScope`](/themes/sdk/reference/product/parts-1#productscope). The cart mutation is host-owned; on the product page `add` runs the page's own add.

**Returns:** The chosen variant, the quantity, the add action and the button text.

**Example**

```tsx
import { AddToCartButton, QuantityStepper, useProductBuy } from '@poltura/theme-sdk/product';

export function BuyBox() {
  const buy = useProductBuy();
  return (
    <div>
      <QuantityStepper value={buy.quantity} onChange={buy.setQuantity} />
      <AddToCartButton
        label={buy.ctaLabel}
        busy={buy.isAdding}
        disabled={!buy.canAdd}
        error={buy.error}
        onAdd={() => void buy.add()}
      />
    </div>
  );
}
```

## `useBundleQuote` [#usebundlequote]

The price of a bundle with the shopper's choice in it, refreshed before it lapses.

```ts
useBundleQuote(input: BundleQuoteInput, options?: Omit<QueryOptions, 'suspense'>): QueryResult<BundleQuote>
```

Pricing is host-owned: the quote is signed, and adding the bundle to the cart presents it back.

**Parameters**

- `input: BundleQuoteInput`: The bundle and the choice to price.
- `options?: Omit<QueryOptions, 'suspense'>`: Whether the read runs, such as off while the choice is incomplete.

**Returns:** The quote once it is priced.

**Example**

```tsx
import { useBundleQuote } from '@poltura/theme-sdk/product';

export function BundlePrice({ bundleId }: { bundleId: string }) {
  const { data } = useBundleQuote({ bundleId });
  return data?.accepted ? <span>{data.total.amount}</span> : null;
}
```

## `useBundleRecommendations` [#usebundlerecommendations]

The bundles recommended for a product, best first, for the shopper's cart.

```ts
useBundleRecommendations(by: {
    readonly productId: string;
    readonly variantId?: string;
}, options?: Omit<QueryOptions, 'suspense'>): QueryResult<readonly BundleRecommendation[]>
```

The ranking is host-owned and seeded by the shopper's cart.

**Parameters**

- `by: { readonly productId: string; readonly variantId?: string; }`: The product to recommend bundles for, and its variant when the shopper picked one.
- `options?: Omit<QueryOptions, 'suspense'>`: Whether the read runs, and whether it keeps the previous answer while loading.

**Returns:** The recommendations once they load; empty when the store recommends none.

**Example**

```tsx
import { useBundleRecommendations } from '@poltura/theme-sdk/product';

export function Suggested({ productId }: { productId: string }) {
  const { data } = useBundleRecommendations({ productId });
  return <ul>{data?.map((row) => <li key={row.bundleId}>{row.title}</li>)}</ul>;
}
```
