# useProducts (/guides/react-hooks/use-products)





useProducts [#useproducts]

`useProducts` 在元件掛載時自動取得產品列表，並管理 loading 和 error 狀態。

<Callout type="info">
  需要在 `RecurProvider` 內使用。
</Callout>

基本用法 [#基本用法]

```tsx
import { useProducts } from 'recur-tw';

function PricingPage() {
  const { data: products, isLoading, error } = useProducts();

  if (isLoading) return <div>載入中...</div>;
  if (error) return <div>載入失敗：{error.message}</div>;

  return (
    <div className="grid grid-cols-3 gap-4">
      {products?.map((product) => (
        <div key={product.id} className="border p-4 rounded">
          <h3>{product.name}</h3>
          <p className="text-2xl font-bold">NT${product.price}/月</p>
          <p>{product.description}</p>
        </div>
      ))}
    </div>
  );
}
```

Options [#options]

| 參數          | 類型                   | 預設     | 說明                                                            |
| ----------- | -------------------- | ------ | ------------------------------------------------------------- |
| `type`      | string               | 全部     | 篩選產品類型：`'SUBSCRIPTION'`、`'ONE_TIME'`、`'CREDITS'`、`'DONATION'` |
| `enabled`   | boolean              | `true` | 設為 `false` 可延遲載入                                              |
| `onSuccess` | `(products) => void` | -      | 載入成功 callback                                                 |
| `onError`   | `(error) => void`    | -      | 載入失敗 callback                                                 |

篩選訂閱型產品 [#篩選訂閱型產品]

```tsx
const { data: plans } = useProducts({ type: 'SUBSCRIPTION' });
```

延遲載入 [#延遲載入]

```tsx
const [ready, setReady] = useState(false);
const { data } = useProducts({ enabled: ready });

// 之後觸發載入
<button onClick={() => setReady(true)}>顯示方案</button>
```

Return 值 [#return-值]

| 屬性          | 類型                    | 說明   |
| ----------- | --------------------- | ---- |
| `data`      | `Product[] \| null`   | 產品列表 |
| `isLoading` | boolean               | 載入中  |
| `error`     | `Error \| null`       | 錯誤物件 |
| `refetch`   | `() => Promise<void>` | 重新載入 |

Product 物件 [#product-物件]

```typescript
interface Product {
  id: string;
  name: string;
  slug: string;
  description?: string;
  price: number;              // TWD 整數（如 499）
  interval?: 'month' | 'year';
  intervalCount?: number;
  type: 'SUBSCRIPTION' | 'ONE_TIME' | 'CREDITS' | 'DONATION';
  trialDays?: number;
  active: boolean;
  metadata?: Record<string, unknown>;
}
```

<Callout type="warning">
  `price` 是 TWD 整數，不需要除以 100。`499` 就是 NT$499。
</Callout>

usePlans（別名） [#useplans別名]

`usePlans` 是 `useProducts({ type: 'SUBSCRIPTION' })` 的語法糖：

```tsx
import { usePlans } from 'recur-tw';

// 等同於 useProducts({ type: 'SUBSCRIPTION' })
const { data: plans } = usePlans();
```

搭配 useSubscribe [#搭配-usesubscribe]

```tsx
import { useProducts, useSubscribe } from 'recur-tw';

function PricingPage() {
  const { data: products, isLoading } = useProducts({ type: 'SUBSCRIPTION' });
  const { subscribe, isLoading: isCheckingOut } = useSubscribe();

  if (isLoading) return <div>載入中...</div>;

  return (
    <div>
      {products?.map((product) => (
        <button
          key={product.id}
          disabled={isCheckingOut}
          onClick={() => subscribe({
            productId: product.id,
            customerEmail: 'user@example.com',
          })}
        >
          {product.name} - NT${product.price}/{product.interval === 'month' ? '月' : '年'}
        </button>
      ))}
    </div>
  );
}
```
