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
.
- ExampleMiniProductQuery.graphql
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:
- HomeScreen.tsx
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:
- HomeScreen.tsx
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 ProductCard
s. 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:
- ExampleMiniProductsQuery.graphql
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:
- HomeScreen.tsx
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 touchesAll the building blocks to render ProductCard
s 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:
- HomeScreen.tsx
- ExampleMiniProductsQuery.graphql
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',
]
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 is the final result: