Since products have variants, the product price may be different dependening on the particular variant. For that reason, it is not possible to ask for a product price. Instead, the product exposes a range of prices as the pricing
field on the Product
type. The range is represented as a tuple with the lowest and the highest price in that range. If all the variants have the same price, the both elements of the tuple are equal.
Let's use the products
query again, but this time we will only list the lowest and the highest price for each product in the collection.
{
products(first: 100, channel: "default-channel") {
edges {
node {
name
pricing {
priceRange {
start {
gross {
amount
}
}
stop {
gross {
amount
}
}
}
}
}
}
}
}
The pricing
field gives a range descriptor (priceRange
) where we can request the lowest (start
) and the highest (stop
) price among each variants of a product. Additionally, the price of a product is always available as a gross, net or just the tax value. In the example above we are interested in displaying the final price to the customer, thus we select the gross value.
Try the query above in the GraphQL Playground. In response you will get a collection of products with names and their price ranges. Most elements will have the same value for the start
and stop
fields, meaning that the product variants don't differ in price.
{
"data": {
"products": {
"edges": [
{
"node": {
"id": "UHJvZHVjdDoxMDc=",
"name": "Saleor Polo Shirt",
"pricing": {
"priceRange": {
"start": {
"gross": {
"amount": 5
}
},
"stop": {
"gross": {
"amount": 5
}
}
}
}
}
},
{
"node": {
"id": "UHJvZHVjdDoxMDg=",
"name": "Saleor T-Shirt",
"pricing": {
"priceRange": {
"start": {
"gross": {
"amount": 4.5
}
},
"stop": {
"gross": {
"amount": 4.5
}
}
}
}
}
}
// ...
]
}
}
}
Let's update the product collection page with the price information displayed along with each product. For simplicity, we will show a single price if both values in the price range are the same. Otherwise we will display two values: the lowest and the highest price for each product.
Open the ProductFilterByName
query located in graphql/queries
and adjust it as shown below:
# graphql/queries/ProductFilterByName.graphql
query ProductFilterByName($first: Int = 4, $after: String) {
products(first: $first, channel: "default-channel", after: $after) {
edges {
node {
id
name
thumbnail {
url
}
category {
name
}
pricing {
priceRange {
start {
gross {
amount
}
}
stop {
gross {
amount
}
}
}
}
}
}
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
totalCount
}
}
Now, let's modify the ProductElement
component to display the price range of a product along with the existing product information.
// components/ProductElement.tsx
import React from 'react';
import Link from 'next/link';
const styles = {
card: 'bg-white border',
summary: 'px-4 py-2 border-gray-100 bg-gray-50 border-t',
description: 'flex justify-between items-center',
title: 'block text-lg text-gray-900 truncate',
category: 'block text-sm font-medium text-gray-500',
image: {
aspect: 'aspect-h-1 aspect-w-1',
content: 'object-center object-cover'
}
}
import { Product } from '@/saleor/api'
type Props = Pick<Product, 'id' | 'name' | 'thumbnail' | 'category' | 'pricing'>;
export const ProductElement = ({ id, name, thumbnail, category, pricing }: Props) => {
const lowestPrice = pricing?.priceRange?.start?.gross.amount ?? 0;
const highestPrice = pricing?.priceRange?.stop?.gross.amount ?? 0;
return (
<li key={id} className={styles.card}>
<Link href={`/product/${id}`}>
<div className={styles.image.aspect}>
<img src={thumbnail?.url} alt="" className={styles.image.content} />
</div>
<div className={styles.summary}>
<div className={styles.description}>
<div>
<p className={styles.title}>{name}</p>
<p className={styles.category}>{category?.name}</p>
</div>
<div>{lowestPrice == highestPrice ? highestPrice : `${lowestPrice} - ${highestPrice}`}</div>
</div>
</div>
</Link>
</li>
);
}
As a result, you should see the price displayed on the right side of each product element in the collection, as shown in the image below: