Product listing grid
Render a product listing grid from listing groups and SDK card helpers.
Product listing grid
Render a product listing grid using
createQueryHooks(client).useProductListingGroupsQuery() and the
buildProductListingCard() SDK helper.
'use client'
import { useMemo } from 'react'
import {
buildProductListingCard,
type ProductListingCard,
} from '@01.software/sdk'
import { createQueryHooks } from '@01.software/sdk/query'
import { useClient } from '@/lib/sdk'
function getMediaUrl(media: ProductListingCard['primaryImage']) {
return media && typeof media === 'object' && 'url' in media
? (media.url ?? null)
: null
}
export function ProductGrid({ productIds }: { productIds: string[] }) {
const client = useClient()
const query = createQueryHooks(client)
const { data } = query.useProductListingGroupsQuery({
options: {
limit: productIds.length || 1,
where: { id: { in: productIds } },
},
})
const cards: ProductListingCard[] = useMemo(
() => data?.docs.map((item) => buildProductListingCard(item)) ?? [],
[data],
)
return (
<ul>
{cards.map((card) => {
const imageUrl = getMediaUrl(card.primaryImage)
return (
<li key={card.id}>
<a href={card.href}>
{imageUrl ? <img src={imageUrl} alt={card.title} /> : null}
<h3>{card.title}</h3>
<p>
{card.priceRange.isPriceRange
? `${card.priceRange.minPrice}–${card.priceRange.maxPrice}`
: card.priceRange.minPrice}
</p>
</a>
{card.swatches.length > 0 && (
<ul aria-label="Available colors">
{card.swatches.map((swatch) => (
<li key={swatch.optionValueId}>
<a
href={swatch.href}
aria-disabled={!swatch.availableForSale}
style={{ background: swatch.swatchColor ?? undefined }}
>
{swatch.label}
</a>
</li>
))}
</ul>
)}
</li>
)
})}
</ul>
)
}The card model is intentionally minimal - it derives only what every
listing card needs. For richer data (brand, categories, tags) read
item.product directly. Each swatch href is a hint-only URL
(?opt.<optionId>=<valueId>); the detail page resolves it through
resolveProductSelection(detail, { search }).
Single-group products emit swatches: []. Storefronts that want a chip
even for a single colorway can fall back to item.groups themselves.