# Next block: Curated products with references https://www.sanity.io/learn/course/editorialized-ecommerce-experiences/next-block-curated-products.md Resolve deeply nested references in your GROQ query and create a polymorphic component to render your content. Your current campaign needs to highlight specific products that are part of the collection. The "products" block allows creators to choose a product, and one of its specific variants. ## Prepare your content In your Sanity studio, update the `body` field with another block. 1. **Add** a "products" block to the `body` field with two references: the **BIZU Trinket Tray** and **ZAPI Incense Burner**. The products block in your body field should now look like this: ![Sanity Studio showing two product references](https://cdn.sanity.io/images/3do82whm/next/705f0c5f63821e3f280f63c021a7b158799a9580-2144x1388.png) Not that each reference in the `products` array contains an additional reference field which allows you to select one of that product's variants. In your Studio files, open the `productWithVariant` schema type to see how these two reference fields work – where the variant reference field is dynamically filtered and validated to only allow variants of the currently selected product. These are the sorts of small details that have extremely high impact for content creators. These references point at up-to-date sources of truth with product content automatically updated from Shopify. ## Resolving references Looking at the _stringified_ version of the block being rendered on the page now, you'll notice `_type: reference` in the data. By default, a GROQ query for a reference will only return its ID. You'll need to update the query for the page in the Hydrogen app. The updated page query below maps over each item in the `body` array, and selectively returns specific data depending on the `_type` of block. 1. **Update** `PAGE_QUERY` to resolve product references in the `products` block. ```typescript:./app/sanity/queries.ts import groq from 'groq'; export const PAGE_QUERY = groq`*[_type == "page" && slug.current == $slug][0]{ ..., body[]{ _key, _type, ..., _type == "products" => { layout, products[]{ _key, productWithVariant { "product": product->{ "title": store.title, "image": store.previewImageUrl, "slug": store.slug, "price": store.priceRange, }, "variant": variant->{ "title": store.title, "image": store.previewImageUrl, "price": store.price, }, } } } } }`; ``` Now this query resolves references and returns data from those documents. However, since the query has changed, its Type will need to be recreated. 1. In your Studio directory, update your Types ```text:Terminal npx sanity@latest typegen generate ``` You should now see the reshaped data being rendered onto the page. ### Rendering the products block Now you'll need to add a component to take this data and render it. Below is some example code for you to paste into your project. In a production project, some of these components would be better split out into their own files to make their use more generic. We're optimising for speed in this module, so it's all contained in one file. 1. **Create** a new component for the Products block ```tsx:./app/components/ProductsBlock.tsx import type {PAGE_QUERYResult, PriceRange} from '~/sanity/sanity.types'; type BodyWithoutNull = NonNullable< NonNullable['body'] >[number]; type ProductsBlockProps = Extract; export function ProductsBlock({products, layout}: ProductsBlockProps) { return Array.isArray(products) ? (
{products.map((product) => layout === 'pill' ? ( ) : ( ), )}
) : null; } type ProductWithVariant = NonNullable[number]; function ProductCard({productWithVariant}: ProductWithVariant) { if (!productWithVariant) { return null; } const productImage = productWithVariant?.variant?.image || productWithVariant?.product?.image; const price = productWithVariant?.variant?.price || productWithVariant?.product?.price; return (
{productImage ? ( {productWithVariant?.product?.title ) : null} {productWithVariant?.product?.title ? (

{productWithVariant.product.title}

) : null} {price ? : null}
); } function ProductPill({productWithVariant}: ProductWithVariant) { if (!productWithVariant) { return null; } const productImage = productWithVariant?.variant?.image || productWithVariant?.product?.image; const price = productWithVariant?.variant?.price || productWithVariant?.product?.price; return (
{productImage ? ( {productWithVariant?.product?.title ) : null}
{productWithVariant?.product?.title ? (

{productWithVariant.product.title}

) : null} {price ? : null}
); } function ProductPrice({price}: {price: PriceRange | number}) { if (typeof price === 'number') { return ${price.toFixed(2)}; } else if ( typeof price.minVariantPrice === 'number' && typeof price.maxVariantPrice === 'number' ) { return ( ${price.minVariantPrice.toFixed(2)} - $ {price.maxVariantPrice.toFixed(2)} ); } return null; } ``` 1. Add the `ProductsBlock` component to the `BLOCKS` object ```tsx:./app/routes/$slug.tsx import {AccordionBlock} from '~/components/AccordionBlock'; import {ProductsBlock} from '~/components/ProductsBlock'; const BLOCKS: Record JSX.Element | null> = { accordion: AccordionBlock, products: ProductsBlock, _unknown: (props: any) =>
{JSON.stringify(props, null, 2)}
, }; ``` You should now see the products block rendered on the page – experiment with the order of products, editing the selected variant, and switching the "layout" field value. ![Two product cards shown inside Sanity Studio's Presentation tool](https://cdn.sanity.io/images/3do82whm/next/e38cd78186be2403af0097ce5035c3765fa6d976-2144x1388.png) ### A note on visual controls in structured content This products block could be considered _polymorphic_ because it contains a field called `layout` which determines whether the products render as "cards" or "pills." These sorts of visual-specific fields should be kept to a minimum and given semantic meaning where possible. For example, a color selector should prompt an author to choose between "primary" or "secondary" instead of "blue" or "red." It may be tempting to begin building all manner of design controls for things like padding, margin, fonts or even raw CSS fields (we've seen it all!) but we strongly caution against this. Where possible, let the front end decide how content should be rendered with smartly applied logic. Turning Sanity into a design tool is a sure way to build rigid, overly complex content schemas that are based around your website design at a point in time – instead of the most meaningful way to author and structure content.