# product (https://docs.salla.dev/twilight/react/api/modules/product)



Use `product` for anything that shows products: a product page, a listing, a slider, a search.

```ts title="app/routes/sale.tsx"
import { getTwilightContext } from '@salla.sa/twilight-theme-engine/tanstack';
import { product } from '@salla.sa/twilight-theme-engine/api/product';

loader: () => {
  const { queryClient } = getTwilightContext();
  return queryClient.fetchQuery(product.queries.list({ source: 'offers' }));
},
```

```tsx title="app/components/CategoryProducts.tsx"
import { useQuery } from '@tanstack/react-query';
import { product } from '@salla.sa/twilight-theme-engine/api/product';

export function CategoryProducts({ categoryId }: { categoryId?: number }) {
  const { data } = useQuery({
    ...product.queries.list({ source: 'categories', sourceValue: [categoryId!] }),
    enabled: categoryId !== undefined,
  });
  const items = data?.items ?? [];

  return <p>{items.length} products</p>;
}
```

## Methods [#methods]

| Method        | Parameters                   | Returns                       | Request                                                                                          | Login needed                                                    |
| ------------- | ---------------------------- | ----------------------------- | ------------------------------------------------------------------------------------------------ | --------------------------------------------------------------- |
| `find`        | `id: string`                 | `Promise<Product>`            | `GET products/{id}/details?with[]=...` (images, category, brand, options, skus, bundle and more) | No                                                              |
| `findOrThrow` | `id: string`                 | `Promise<Product>`            | Same as `find`                                                                                   | No                                                              |
| `list`        | `params: ProductsListParams` | `Promise<ProductsListResult>` | `GET products?source=...&per_page=...`                                                           | No (`source: 'wishlist'` and `'reorder'` are customer-specific) |

`ProductsListParams`:

| Param                           | Description                                                                                                                                                                                           |
| ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `source`                        | Required. One of `brands`, `categories`, `json`, `landing-page`, `latest`, `offers`, `recently`, `related`, `selected`, `tags`, `top-rated`, `wishlist`, `search`, `sales`, `reorder`, `best_selling` |
| `sourceValue`                   | The ids the source reads (category, brand, tag or product ids), as an array such as `[708738657]`; sent as `source_value[]`                                                                           |
| `keyword`                       | The search words when `source` is `'search'`                                                                                                                                                          |
| `page`, `cursor`                | Paging. Use `cursor` (from `next`); `page` is sent only if you set it                                                                                                                                 |
| `perPage`                       | Items per page, default 16                                                                                                                                                                            |
| `sort`, `filterable`, `filters` | Sorting and filters                                                                                                                                                                                   |
| `signal`                        | An `AbortSignal`                                                                                                                                                                                      |

The result is `{ items: Product[]; next: string | null; filters?: Filter[] }`. Pass `next` back as `cursor` for the following page; it is `null` on the last page.

## Queries [#queries]

| Query                          | Key                                       |
| ------------------------------ | ----------------------------------------- |
| `product.queries.detail(id)`   | `['products', 'detail', id, { scope }]`   |
| `product.queries.list(params)` | `['products', 'list', params, { scope }]` |

Both keys include the active scope (the customer's branch) because stock depends on it. Neither includes the locale; see [Authentication and scope](https://docs.salla.dev/twilight/react/api/authentication-and-scope).

## Gotchas [#gotchas]

* `find` throws on an unknown id. In a loader use `findOrThrow` to get a not-found page for a 404 or 410 (other errors still throw).
* For `source: 'search'` put the words in `keyword`. Every other source needs `sourceValue` as ids.
* With `source: 'categories'`, a `category_id` entry in `filters` replaces `sourceValue`.
* Items returned by `list` are remembered, so `queries.detail(id)` can show the card data as placeholder while the full details load.
* There are no mutations. Invalidate `['products']` if the customer's branch changes.

## See also [#see-also]

* [wishlist](https://docs.salla.dev/twilight/react/api/modules/wishlist), which reuses the list shape
* [Product route](https://docs.salla.dev/twilight/react/routes/product) and [useProduct](https://docs.salla.dev/twilight/react/hooks/use-product)
* [Authentication and scope](https://docs.salla.dev/twilight/react/api/authentication-and-scope)
