> For the complete documentation index, see [llms.txt](https://docs.elitebundleapp.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.elitebundleapp.com/guides/react.md).

# Using with React

The SDK is framework-agnostic — it exposes plain JavaScript objects and events. This guide shows patterns for consuming the SDK inside a React app embedded in a Shopify theme.

***

## Setup

Install your React app however you like (a `<script type="module">` tag, Vite, or a bundled file). It just needs to run on the same page as the bundle.

The SDK is always available at `window.eliteBundle` — no install or import required. You access it from React via refs and effects, the same as any other global.

***

## 1. Wait for ready and sync to state

Use a `useEffect` to subscribe to the ready event, then mirror the SDK state into React.

```tsx
import { useState, useEffect, useCallback } from 'react';

interface ByobSDK {
  steps: any[];
  cart: any[];
  cartTotalFormatted: string;
  canCheckout: boolean;
  addToCart: (product: any, variantId: string, stepId?: string) => void;
  checkout: (target?: string) => Promise<void>;
  formatPrice: (amount: number) => string;
}

function useBundleSDK() {
  const [sdk, setSdk] = useState<ByobSDK | null>(null);
  const [isReady, setIsReady] = useState(false);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    if (!window.eliteBundle?.sdk) return; // SDK not enabled

    function onReady() {
      setSdk(window.eliteBundle.sdk.byob);
      setIsReady(true);
    }

    function onError(e: CustomEvent<{ error: string }>) {
      setError(e.detail.error);
    }

    document.addEventListener('elite:byob:ready', onReady);
    document.addEventListener('elite:byob:error', onError as EventListener);

    // Handle ready already fired
    const current = window.eliteBundle.sdk.byob;
    if (current && !current.isLoading && !current.error) onReady();

    return () => {
      document.removeEventListener('elite:byob:ready', onReady);
      document.removeEventListener('elite:byob:error', onError as EventListener);
    };
  }, []);

  return { sdk, isReady, error };
}
```

***

## 2. Sync cart state from SDK events

The SDK state updates on every React render inside the bundle iframe, but your React app is a separate tree. Use events to get live cart updates.

```tsx
function useByobCart() {
  const [cart, setCart] = useState([]);
  const [cartTotal, setCartTotal] = useState('$0.00');
  const [canCheckout, setCanCheckout] = useState(false);

  useEffect(() => {
    function onCartChange(e: CustomEvent) {
      const { cart, cartTotalFormatted, canCheckout } = e.detail;
      setCart(cart);
      setCartTotal(cartTotalFormatted);
      setCanCheckout(canCheckout);
    }

    document.addEventListener('elite:byob:cart-change', onCartChange as EventListener);
    return () => {
      document.removeEventListener('elite:byob:cart-change', onCartChange as EventListener);
    };
  }, []);

  return { cart, cartTotal, canCheckout };
}
```

***

## 3. Example component

```tsx
function BundleBuilder() {
  const { sdk, isReady, error } = useBundleSDK();
  const { cart, cartTotal, canCheckout } = useByobCart();
  const [isCheckingOut, setIsCheckingOut] = useState(false);

  const handleCheckout = useCallback(async () => {
    if (!sdk || !canCheckout) return;
    setIsCheckingOut(true);
    try {
      await sdk.checkout('checkout');
    } finally {
      setIsCheckingOut(false);
    }
  }, [sdk, canCheckout]);

  if (error) return <p>Error: {error}</p>;
  if (!isReady || !sdk) return <p>Loading…</p>;

  return (
    <div>
      {sdk.steps.map(step => (
        <StepSection key={step.id} step={step} sdk={sdk} />
      ))}

      <CartDrawer
        cart={cart}
        cartTotal={cartTotal}
        canCheckout={canCheckout}
        isCheckingOut={isCheckingOut}
        onCheckout={handleCheckout}
      />
    </div>
  );
}
```

***

## 4. Per-card quantity state

Instead of storing quantities in React state (which would require diffing the cart array on every change), read them directly from the SDK on demand.

```tsx
function ProductCard({ product, variant, stepId }) {
  const [qty, setQty] = useState(0);

  useEffect(() => {
    function sync() {
      const sdk = window.eliteBundle?.sdk?.byob;
      if (!sdk) return;
      setQty(sdk.getItemQuantity(variant.id, stepId));
    }

    document.addEventListener('elite:byob:cart-change', sync);
    sync(); // initialize

    return () => document.removeEventListener('elite:byob:cart-change', sync);
  }, [variant.id, stepId]);

  const handleAdd = () => {
    const sdk = window.eliteBundle?.sdk?.byob;
    sdk?.addToCart(product, variant.id, stepId);
  };

  const handleInc = () => {
    window.eliteBundle?.sdk?.byob?.updateQuantity(variant.id, 1, stepId);
  };

  const handleDec = () => {
    window.eliteBundle?.sdk?.byob?.updateQuantity(variant.id, -1, stepId);
  };

  return (
    <div>
      <img src={product.featuredImage?.url} alt={product.title} />
      <p>{product.title}</p>
      {qty > 0 ? (
        <div>
          <button onClick={handleDec}>−</button>
          <span>{qty}</span>
          <button onClick={handleInc}>+</button>
        </div>
      ) : (
        <button onClick={handleAdd} disabled={!variant.available}>
          {variant.available ? 'Add' : 'Sold out'}
        </button>
      )}
    </div>
  );
}
```

***

## 5. MOB in React

Same pattern — subscribe to `elite:mob:ready`, then use `elite:mob:selection-change` for reactive updates.

```tsx
function useMobSDK() {
  const [sdk, setSdk] = useState(null);
  const [selection, setSelection] = useState({
    allSelected: false,
    totalPriceFormatted: '$0.00',
    discountedPriceFormatted: null,
  });

  useEffect(() => {
    if (!window.eliteBundle?.sdk) return;

    function onReady() {
      setSdk(window.eliteBundle.sdk.mob);
    }

    function onSelectionChange(e) {
      setSelection({
        allSelected: e.detail.allSelected,
        totalPriceFormatted: e.detail.totalPriceFormatted,
        discountedPriceFormatted: e.detail.discountedPriceFormatted,
      });
    }

    document.addEventListener('elite:mob:ready', onReady);
    document.addEventListener('elite:mob:selection-change', onSelectionChange);

    const current = window.eliteBundle.sdk.mob;
    if (current && !current.isLoading && !current.error) onReady();

    return () => {
      document.removeEventListener('elite:mob:ready', onReady);
      document.removeEventListener('elite:mob:selection-change', onSelectionChange);
    };
  }, []);

  return { sdk, selection };
}

function MobPicker() {
  const { sdk, selection } = useMobSDK();

  if (!sdk) return <p>Loading…</p>;

  return (
    <div>
      {sdk.options.map((option, i) => (
        <OptionSection
          key={i}
          option={option}
          items={sdk.selectableItems[i] ?? []}
          selectedKey={sdk.selectedItemKeys[i]}
          onSelect={(key) => sdk.selectItem(i, key)}
        />
      ))}

      <p>
        {selection.discountedPriceFormatted ? (
          <>
            <s>{selection.totalPriceFormatted}</s>{' '}
            <strong>{selection.discountedPriceFormatted}</strong>
          </>
        ) : selection.totalPriceFormatted}
      </p>

      <button
        disabled={!selection.allSelected}
        onClick={() => sdk.addToCart('checkout')}
      >
        Add bundle to cart
      </button>
    </div>
  );
}
```

***

## TypeScript

Import the SDK types from the package if you need them. Since they're not exported as an npm package, copy the relevant interfaces from [BYOB SDK Reference](/reference/byob.md) and [MOB SDK Reference](/reference/mob.md) into your own `types.ts`.

Or add a global declaration to extend the `Window` type:

```ts
// types.d.ts
declare global {
  interface Window {
    eliteBundle: {
      version: string;
      app: Record<string, unknown>;
      byob: Record<string, unknown> | null;
      mob: Record<string, unknown> | null;
      sdk?: {
        byob: any | null;
        mob: any | null;
      };
      on(event: string, cb: EventListener): void;
      off(event: string, cb: EventListener): void;
    };
  }
}
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.elitebundleapp.com/guides/react.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
