> ## Documentation Index
> Fetch the complete documentation index at: https://swish.app/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Hooks

> Reference for all React SDK hooks - query saved items, invoke intents, manage save state, and track item counts.

All hooks must be used inside a [`SwishProvider`](https://swish.app/docs/references/react/provider). They return `null` or loading states while the SDK initializes.

## `useSwish`

Returns the Swish SDK instance.

```ts theme={null}
const swish = useSwish();
```

### Return value

| Type | Description |
| - | - |
| `SwishApp \| null` | The SDK instance, or `null` while loading. |

Throws an error if called outside a `SwishProvider`.

> [!NOTE]
>
> If `window.swish` exists (for example, the SDK was loaded via a script tag),
> the hook returns the global instance instead of the context value.

## `useSwishItem`

Manage save state for a single product. Handles lookups, save/unsave, list editing, and variant changes.

```tsx theme={null}
const { saved, toggle, loading } = useSwishItem({
  productId: "gid://shopify/Product/8234567890123",
  variantId: "gid://shopify/ProductVariant/44234567890123",
});
```

### Props

| Prop | Type | Required | Description |
| - | - | - | - |
| `productId` | `string \| number` | Yes | Shopify product ID or GID. |
| `variantId` | `string \| number` | No | Shopify variant ID or GID. |
| `itemId` | `string` | No | Known saved item ID. Skips the lookup query when provided. |
| `match` | `"variant" \| "product" \| "auto"` | No | How saved state is interpreted and what `save()` targets. Defaults to `"auto"` - see [match policy](#match-policy). |
| `quantity` | `number` | No | Quantity for the item `save()` creates. Defaults to `1`. |
| `onSave` | `(data) => void` | No | Called after a successful save. Receives the saved item, product, and variant. |
| `onUnsave` | `(data) => void` | No | Called after a successful unsave. Receives the ID of the removed item. |
| `onListsUpdate` | `(data) => void` | No | Called after list assignments change. Receives the updated item, product, and variant. |
| `onVariantUpdate` | `(data) => void` | No | Called after the variant is changed. Receives the updated item, product, and new variant. |

### Return value

| Property | Type | Description |
| - | - | - |
| `saved` | `boolean` | Whether the product is currently saved (per the `match` policy). |
| `loading` | `boolean` | `true` while querying or saving. |
| `saving` | `boolean` | `true` while a save operation is in progress. |
| `unsaving` | `boolean` | `true` while an unsave operation is in progress. |
| `error` | `ApiError \| null` | API error, if any. |
| `savedItemId` | `string \| null` | The saved item ID picked by the `match` policy, or `null` if not saved. |
| `savedItemIds` | `string[]` | Every saved item ID for the product, regardless of `match`. Useful for "saved N times" UIs. |
| `savedItemQuantity` | `number \| null` | Quantity stored on the `savedItemId` item; `null` when not saved or when `itemId` was passed in. |
| `savedItemVariantId` | `number \| null` | Variant stored on the `savedItemId` item; `null` when not saved, saved without a variant, or when `itemId` was passed in. |
| `save` | `() => Promise<void>` | Save the product. Triggers the `save:item` intent. |
| `unsave` | `() => Promise<void>` | Remove the saved item. Triggers the `unsave:item` intent. |
| `toggle` | `() => Promise<void>` | Save if unsaved, unsave if saved. |
| `updateLists` | `() => Promise<void>` | Open the list editor for this item. Triggers the `edit:item-lists` intent. |
| `updateVariant` | `() => Promise<void>` | Open the variant selector. Triggers the `edit:item-variant` intent. |

The hook automatically queries the API to determine if the product or variant is already saved. Providing `itemId` directly skips this query.

#### Match policy

The hook always loads saved state at the product level - a single cache entry per product is shared across every variant-aware component on the page. The `match` prop decides what counts as "saved":

* `"variant"`: only saved if `variantId` matches a saved item.
* `"product"`: saved if any variant of the product is saved. Use on collection cards that show a default variant but should reflect the product-level wishlist state.
* `"auto"` (default): variant-match when `variantId` is set, product-match otherwise.

`match` also affects what gets saved: with `match: "product"`, `save()` ignores `variantId` and saves the product, so a card that just renders the default variant for display doesn't create a variant-bound saved item. `unsave()` always targets the currently picked `savedItemId`.

```tsx theme={null}
function ProductCard({ productId, defaultVariantId }) {
  const { saved, toggle } = useSwishItem({
    productId,
    variantId: defaultVariantId,
    match: "product",
  });

  return (
    <button type="button" onClick={toggle}>
      {saved ? "Saved" : "Save"}
    </button>
  );
}
```

```tsx theme={null}
function ProductCard({ productId }: { productId: string }) {
  const { saved, toggle, loading, updateLists } = useSwishItem({
    productId,
    onSave: (data) => console.log("Saved item:", data.item.id),
  });

  return (
    <div>
      <button type="button" onClick={toggle} disabled={loading}>
        {saved ? "Saved" : "Save"}
      </button>
      {saved && <button type="button" onClick={updateLists}>Edit lists</button>}
    </div>
  );
}
```

## `useSwishItems`

Load saved items with pagination.

```ts theme={null}
const { data, hasMore, loadMore, loading } = useSwishItems({ limit: 12 });
```

### Options

| Option | Type | Default | Description |
| - | - | - | - |
| `listId` | `string` | - | Filter items by list. |
| `limit` | `number` | `24` | Number of items per page. |

### Return value

| Property | Type | Description |
| - | - | - |
| `data` | `Item[]` | Accumulated items across all loaded pages. |
| `loading` | `boolean` | `true` while fetching. |
| `error` | `ApiError \| null` | API error, if any. |
| `pageInfo` | `PageInfo \| null` | Pagination metadata. |
| `hasMore` | `boolean` | `true` if more pages are available. |
| `loadMore` | `() => void` | Fetch the next page. Items are appended to `data`. |
| `reset` | `() => void` | Clear all loaded data and pagination state. |

```tsx theme={null}
function WishlistPage() {
  const { data: items, hasMore, loadMore, loading } = useSwishItems();

  return (
    <div>
      {items.map((item) => (
        <div key={item.id}>{item.productId}</div>
      ))}
      {hasMore && (
        <button type="button" onClick={loadMore} disabled={loading}>
          Load more
        </button>
      )}
    </div>
  );
}
```

## `useSwishLists`

Load the customer's lists.

```ts theme={null}
const { lists, loading, error } = useSwishLists();
```

### Return value

| Property | Type | Description |
| - | - | - |
| `lists` | `List[] \| null` | The customer's lists, or `null` while loading. |
| `loading` | `boolean` | `true` while fetching. |
| `error` | `ApiError \| null` | API error, if any. |
| `reset` | `() => void` | Clear data and refetch. |

Automatically refetches when items or lists change (`item-lists-update`, `item-delete`, `list-create`, `list-update`, `list-delete`).

```tsx theme={null}
function ListSidebar() {
  const { lists, loading } = useSwishLists();

  if (loading || !lists) return <p>Loading lists...</p>;

  return (
    <ul>
      {lists.map((list) => (
        <li key={list.id}>{list.name}</li>
      ))}
    </ul>
  );
}
```

## `useSwishItemCount`

Track the total number of saved items.

```ts theme={null}
const { count, loading, refetching } = useSwishItemCount();
```

### Return value

| Property | Type | Description |
| - | - | - |
| `count` | `number` | Total saved item count. Defaults to `0` while the first fetch is in flight. |
| `loading` | `boolean` | `true` during the first fetch only. |
| `refetching` | `boolean` | `true` while a later fetch is in flight (`item-create`, `item-delete`, or `token-update`). |
| `error` | `ApiError \| null` | API error, if any. |

Automatically refetches when items are created or deleted (`item-create`, `item-delete`). `loading` matches [`swish.state.itemCount()`](https://swish.app/docs/references/sdk/state#itemcount): a badge that hides itself while `loading` will not blink empty on a later refetch. The last known `count` stays until the new number lands.

```tsx theme={null}
function WishlistBadge() {
  const { count } = useSwishItemCount();

  if (count === 0) return null;

  return <span className="badge">{count}</span>;
}
```

## `useSwishQuery`

Generic hook for making Swish API calls with automatic refetching on events.

```ts theme={null}
const { data, loading, error, refetch } = useSwishQuery(
  (api) => api.items.list({ limit: 5 }),
  { refetch: true },
);
```

### Parameters

| Parameter | Type | Required | Description |
| - | - | - | - |
| `fetch` | `(api, variables?) => Promise<Response>` | Yes | Function that receives `swish.api` and returns a promise. |
| `options.variables` | `TVariables` | No | Variables passed as the second argument to `fetch`. Re-runs the query when values change. |
| `options.skip` | `boolean` | No | Skip the query when `true`. |
| `options.refetch` | `boolean \| EventName[]` | No | `true` for default events, or an array of specific event names. |

When `refetch` is `true`, the query re-runs on these default events: `item-create`, `item-update`, `item-delete`, `item-lists-update`, `list-create`, `list-update`, `list-delete`, `token-update`.

When `refetch` is an array, `token-update` is always included automatically.

### Return value

| Property | Type | Description |
| - | - | - |
| `data` | `Data \| null` | The response data, extracted from `response.data`. |
| `pageInfo` | `PageInfo \| null` | Pagination info for paginated responses. |
| `error` | `ApiError \| null` | API error, if any. |
| `loading` | `boolean` | `true` while the query is in flight. |
| `refetching` | `boolean` | `true` when refetching after initial load. |
| `refetch` | `() => void` | Manually trigger a refetch. |
| `reset` | `() => void` | Clear data, error, and loading state. |

```tsx theme={null}
function RecentOrders() {
  const { data: orders, loading } = useSwishQuery((api) =>
    api.orders.list({ limit: 3 }),
  );

  if (loading || !orders) return null;

  return (
    <ul>
      {orders.map((order) => (
        <li key={order.id}>Order #{order.id}</li>
      ))}
    </ul>
  );
}
```

## `useSwishIntent`

Invoke a Swish intent and track its pending state. See the [intents reference](https://swish.app/docs/references/sdk/intents.md) for available intents and their parameters.

```ts theme={null}
const { invoke, pending } = useSwishIntent("save:item", {
  productId: "gid://shopify/Product/8234567890123",
});
```

### Parameters

| Parameter | Type | Required | Description |
| - | - | - | - |
| `intent` | `Intent` | Yes | The intent name (e.g. `"save:item"`, `"view:home"`). |
| `data` | `IntentToData<I>` | Depends | Intent-specific data. Required for intents that accept parameters, omitted for parameterless intents like `"view:home"`. |

### Return value

| Property | Type | Description |
| - | - | - |
| `invoke` | `() => Promise<IntentResponse<I>>` | Trigger the intent. Returns the intent response. |
| `pending` | `boolean` | `true` while the intent is in progress. |

The response contains a `code` field (`"ok"`, `"closed"`, or `"error"`) and intent-specific `data` on success. See the [intents reference](https://swish.app/docs/references/sdk/intents.md#response-format) for the response format.

```tsx theme={null}
function QuickBuyButton({ productId }: { productId: string }) {
  const { invoke: openQuickBuy, pending } = useSwishIntent("initiate:quick-buy", {
    productId,
  });

  return (
    <button type="button" onClick={openQuickBuy} disabled={pending}>
      Quick buy
    </button>
  );
}
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.