Skip to main content

Building a products screen

Previously, you fetched data from your storefront, including your store’s name and products. Your home screen is rendering the shop's name. You're now ready to render products using the Shop Minis SDK.

What will you learn?

In this tutorial, you'll learn how to do the following tasks:

  • Query more products from the Minis API.
  • Render products with the available Minis SDK components.
  • Navigate the Shop user to the product's detail screen.

Step 1: Render a product card

First you need to fetch more data from our product. On the previous page you asked for the product's title and featureImage.url. In order for us to render a ProductCard you will need more information about the product.

Under ExampleMiniProductQuery.graphql you'll find the changes required to render the ProductCard.

query ExampleMiniProductQuery($shopId: ID!, $productId: ID!) {
shop(id: $shopId) {
id
name
product(id: $productId) {
id
title
featuredImage {
id
url
}
defaultVariant {
id
title
isFavorited
price {
amount
currencyCode
}
image {
id
url
}
compareAtPrice {
amount
currencyCode
}
}
}
}
}

After making the changes, run:

npx shop-minis generate-graphql-types

Alternatively you can run the command in watch mode, so all future changes in any .graphql file will update the type files.

npx shop-minis generate-graphql-types --watch

A new file ExampleMiniProductQuery.graphql.d.ts will be created alongisde ExampleMiniProductQuery.graphql. Now when using useMinisQuery TypeScript will have the correct types for the data object.

After, on HomeScreen.tsx, you will be able to fetch product from our shop. Now you can render the ProductCard component, since you have the shop id and the product object:

import {
Box,
ProductCard,
Text,
useMinisQuery,
} from '@shopify/shop-minis-platform-sdk'

import ExampleMiniProductQuery from './ExampleMiniProductQuery.graphql'

export const HomeScreen = () => {
const {data} = useMinisQuery(ExampleMiniProductQuery, {
variables: {
shopId: 'gid://shopify/Shop/68822335510',
productId: 'gid://shopify/Product/7982542651414',
},
})

return (
<Box padding="m">
{data?.shop?.name ? (
<Text variant="headerNormal" marginBottom="s">
{data?.shop?.name}
</Text>
) : null}

{data?.shop?.product ? (
<ProductCard shopId={data?.shop.id} product={data?.shop?.product} />
) : null}
</Box>
)
}

The product data is fetched and rendered:

Step 2: Make it interactive

In this step you will make the ProductCard interactive. To navigate a Shop user to a product detail screen, you need to wrap a component with the TouchableProduct component.

<TouchableProduct product={product}>
<Text>Hey, click on me to navigate to the product</Text>
</TouchableProduct>

Notice you can wrap any component with a TouchableProduct.

Let's begin by creating two constants to hold the shop and product objects:

const shop = data?.shop
const product = data?.shop?.product

Then, to avoid working with possible null or undefined values, a Spinner component can be returned while the data is loading:

if (!shop || !product) {
return (
<Box flex={1} justifyContent="center" alignItems="center">
<Spinner />
</Box>
)
}

Applying TouchableProduct on our Home screen should be straight-forward now, by simply wrapping our ContentCard with it:

<TouchableProduct product={product}>
<ProductCard shopId={shop.id} product={product} />
</TouchableProduct>

The final changes should look like:

import {
Box,
ProductCard,
Spinner,
Text,
TouchableProduct,
useMinisQuery,
} from '@shopify/shop-minis-platform-sdk'

import ExampleMiniProductQuery from './ExampleMiniProductQuery.graphql'

export const HomeScreen = () => {
const {data} = useMinisQuery(ExampleMiniProductQuery, {
variables: {
shopId: 'gid://shopify/Shop/68822335510',
productId: 'gid://shopify/Product/7982542651414',
},
})
const shop = data?.shop
const product = data?.shop?.product

if (!shop || !product) {
return (
<Box flex={1} justifyContent="center" alignItems="center">
<Spinner />
</Box>
)
}

return (
<Box padding="m">
<Text variant="headerNormal" marginBottom="s">
{shop.name}
</Text>

<TouchableProduct product={product}>
<ProductCard shopId={shop.id} product={product} />
</TouchableProduct>
</Box>
)
}

Tapping on the ContentCard component now displays the product detail screen to the user:

Step 3: Rendering a product cards grid

Next, you'll learn how to render a grid of ProductCards. First you'll need to update the query to fetch more than one product. Let's rename it to ExampleMiniProductsQuery.graphql and update the content to:

query ExampleMiniProductsQuery($shopId: ID!, $productIds: [ID!]!) {
shop(id: $shopId) {
id
name
productsByIds(ids: $productIds) {
id
title
featuredImage {
id
url
}
defaultVariant {
id
title
isFavorited
price {
amount
currencyCode
}
image {
id
url
}
compareAtPrice {
amount
currencyCode
}
}
}
}
}

This query requires an array of product ids. For this tutorial, you can use this set of products from our testing store:

const PRODUCT_GIDS = [
'gid://shopify/Product/7982542651414',
'gid://shopify/Product/7982528397334',
'gid://shopify/Product/7982535704598',
'gid://shopify/Product/7982577352726',
'gid://shopify/Product/7982564245526',
'gid://shopify/Product/7982547763222',
'gid://shopify/Product/7982499495958',
'gid://shopify/Product/7982540521494',
'gid://shopify/Product/7982554710038',
'gid://shopify/Product/7982557200406',
'gid://shopify/Product/7982571257878',
'gid://shopify/Product/7982372388886',
]

Re-generate the GraphQL types, if you're not running the command in watch mode. Then import ExampleMiniProductsQuery.graphql and use it from our HomeScreen:

import ExampleMiniProductsQuery from './ExampleMiniProductsQuery.graphql'

export const HomeScreen = () => {
const {data} = useMinisQuery(ExampleMiniProductsQuery, {
variables: {
shopId: 'gid://shopify/Shop/68822335510',
productIds: PRODUCT_GIDS,
},
})
const shop = data?.shop
const products = data?.shop?.productsByIds

if (!shop || !products) {

At this point you'll be able to use the ProductCardGrid. It behaves similarly to React Native's FlatList:

<ProductCardGrid
products={products}
renderItem={({product}) => {
return (
<TouchableProduct product={product}>
<ProductCard shopId={shop.id} product={product} />
</TouchableProduct>
)
}}
/>

The end result should look like this:

import {
Box,
ProductCard,
ProductCardGrid,
Spinner,
Text,
TouchableProduct,
useMinisQuery,
} from '@shopify/shop-minis-platform-sdk'

import ExampleMiniProductsQuery from './ExampleMiniProductsQuery.graphql'

export const HomeScreen = () => {
const {data} = useMinisQuery(ExampleMiniProductsQuery, {
variables: {
shopId: 'gid://shopify/Shop/68822335510',
productIds: PRODUCT_GIDS,
},
})
const shop = data?.shop
const products = data?.shop?.productsByIds

if (!shop || !products) {
return (
<Box flex={1} justifyContent="center" alignItems="center">
<Spinner />
</Box>
)
}

return (
<Box padding="m" flex={1}>
<Text variant="headerNormal" marginBottom="s">
{shop.name}
</Text>

<ProductCardGrid
products={products}
renderItem={({product}) => {
return (
<TouchableProduct product={product}>
<ProductCard shopId={shop.id} product={product} />
</TouchableProduct>
)
}}
/>
</Box>
)
}

const PRODUCT_GIDS = [
'gid://shopify/Product/7982542651414',
'gid://shopify/Product/7982528397334',
'gid://shopify/Product/7982535704598',
'gid://shopify/Product/7982577352726',
'gid://shopify/Product/7982564245526',
'gid://shopify/Product/7982547763222',
'gid://shopify/Product/7982499495958',
'gid://shopify/Product/7982540521494',
'gid://shopify/Product/7982554710038',
'gid://shopify/Product/7982557200406',
'gid://shopify/Product/7982571257878',
'gid://shopify/Product/7982372388886',
]

How it should look like:

## Step 4: Finishing touches

All the building blocks to render ProductCards and a ProductCardGrid are defined. What can be done now is to add a simple error handling state. Using the error object from useMinisQuery:

const {data, error} = useMinisQuery(ExampleMiniProductsQuery, {

You can check if anything failed during the query, and if it did to return a message to the user:

if (error) {
return (
<Box flex={1} justifyContent="center" alignItems="center">
<Text variant="bodyLargeBold" marginBottom="s">
Oops! Something went wrong.
</Text>
<Text marginBottom="s">{error.message}</Text>
</Box>
)
}

If you want to display an error alert or even stop the Mini altogether, check this error handling reference.

The Mini theme object is available via the useTheme hook. This way we can add some padding inside our ProductCardGrid:

const theme = useTheme()

// Data fetching and error, loading state handling

return (
<Box flex={1}>
<Text variant="headerNormal" paddingHorizontal="m" paddingVertical="s">
{shop.name}
</Text>

<ProductCardGrid
products={products}
renderItem={({product}) => {
return (
<TouchableProduct product={product}>
<ProductCard shopId={shop.id} product={product} />
</TouchableProduct>
)
}}
contentContainerStyle={{
paddingHorizontal: theme.spacing.m,
paddingBottom: theme.spacing.m,
}}
/>
</Box>
)

Conclusion

With this introduction to the Shop Minis SDK you're now able to render products.

For more reference on what the Shop Minis SDK can offer, create a new Mini using our Shoppable Posts and Shoppable Videos templates. There you can find example of how to use many of the available components.

Below you'll find the final version of what you coded:

import {
Box,
ProductCard,
ProductCardGrid,
Spinner,
Text,
TouchableProduct,
useMinisQuery,
useTheme,
} from '@shopify/shop-minis-platform-sdk'

import ExampleMiniProductsQuery from './ExampleMiniProductsQuery.graphql'

export const HomeScreen = () => {
const theme = useTheme()
const {data, error} = useMinisQuery(ExampleMiniProductsQuery, {
variables: {
shopId: 'gid://shopify/Shop/68822335510',
productIds: PRODUCT_GIDS,
},
})
const shop = data?.shop
const products = data?.shop?.productsByIds

if (error) {
return (
<Box flex={1} justifyContent="center" alignItems="center">
<Text variant="bodyLargeBold" marginBottom="s">
Oops! Something went wrong.
</Text>
<Text marginBottom="s">{error.message}</Text>
</Box>
)
}

if (!shop || !products) {
return (
<Box flex={1} justifyContent="center" alignItems="center">
<Spinner />
</Box>
)
}

return (
<Box flex={1}>
<Text variant="headerNormal" paddingHorizontal="m" paddingVertical="s">
{shop.name}
</Text>

<ProductCardGrid
products={products}
renderItem={({product}) => {
return (
<TouchableProduct product={product}>
<ProductCard shopId={shop.id} product={product} />
</TouchableProduct>
)
}}
contentContainerStyle={{
paddingHorizontal: theme.spacing.m,
paddingBottom: theme.spacing.m,
}}
/>
</Box>
)
}

const PRODUCT_GIDS = [
'gid://shopify/Product/7982542651414',
'gid://shopify/Product/7982528397334',
'gid://shopify/Product/7982535704598',
'gid://shopify/Product/7982577352726',
'gid://shopify/Product/7982564245526',
'gid://shopify/Product/7982547763222',
'gid://shopify/Product/7982499495958',
'gid://shopify/Product/7982540521494',
'gid://shopify/Product/7982554710038',
'gid://shopify/Product/7982557200406',
'gid://shopify/Product/7982571257878',
'gid://shopify/Product/7982372388886',
]

This is the final result: