- Get Started
- Product
- Resources
- Tools & SDKs
- Framework
- Reference
Menu
- Get Started
- Product
- Resources
- Tools & SDKs
- Framework
- Reference
Example: Show Product Variant's Sale Price
In this document, you'll learn how to display a product variant's sale price, with a full React example.
Check if a Price is a Sale#
To check if a product variant's price is a sale price, check whether the variant's calculated_price.calculated_price.price_list_type
field is equal to sale
:
Where selectedVariantPrice
is either the variant the customer selected or the cheapest variant.
Display Original and Discount Amounts#
If the price is a sale price, the original price is in the variant's calculated_price.original_amount
field:
1const salePrice = formatPrice(selectedVariantPrice.calculated_price.calculated_amount)2const originalPrice = formatPrice(selectedVariantPrice.calculated_price.original_amount)3const discountedAmount = formatPrice(4 selectedVariantPrice.calculated_price.original_amount - 5 selectedVariantPrice.calculated_price.calculated_amount6)
You can use the original price either to display it or calculate and display the discounted amount.
Full React Example#
For example, in a React-based storefront:
Note: The example only passes the
region_id
query parameter for pricing. Learn how to store and retrieve the customer's region in the Regions guides .1"use client" // include with Next.js 13+2 3import { useEffect, useMemo, useState } from "react"4import { HttpTypes } from "@medusajs/types"5import { useRegion } from "../providers/region"6 7type Params = {8 params: {9 id: string10 }11}12 13export default function Product({ params: { id } }: Params) {14 const [loading, setLoading] = useState(true)15 const [product, setProduct] = useState<16 HttpTypes.StoreProduct | undefined17 >()18 const [selectedOptions, setSelectedOptions] = useState<Record<string, string>>({})19 const { region } = useRegion()20 21 useEffect(() => {22 if (!loading) {23 return 24 }25 26 const queryParams = new URLSearchParams({27 fields: `*variants.calculated_price`,28 region_id: region.id,29 })30 31 fetch(`http://localhost:9000/store/products/${id}?${queryParams.toString()}`, {32 credentials: "include",33 headers: {34 "x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",35 },36 })37 .then((res) => res.json())38 .then(({ product: dataProduct }) => {39 setProduct(dataProduct)40 setLoading(false)41 })42 }, [loading])43 44 const selectedVariant = useMemo(() => {45 if (46 !product?.variants ||47 !product.options || 48 Object.keys(selectedOptions).length !== product.options?.length49 ) {50 return51 }52 53 return product.variants.find((variant) => variant.options?.every(54 (optionValue) => optionValue.value === selectedOptions[optionValue.option_id!]55 ))56 }, [selectedOptions, product])57 58 const formatPrice = (amount: number): string => {59 return new Intl.NumberFormat("en-US", {60 style: "currency",61 currency: region.currency_code,62 })63 .format(amount)64 }65 66 const selectedVariantPrice = useMemo(() => {67 if (selectedVariant) {68 return selectedVariant69 }70 71 return product?.variants?.sort((a: any, b: any) => {72 return (73 a.calculated_price.calculated_amount -74 b.calculated_price.calculated_amount75 )76 })[0]77 }, [selectedVariant, product])78 79 const price = useMemo(() => {80 if (!selectedVariantPrice) {81 return82 }83 84 // @ts-ignore85 return formatPrice(selectedVariantPrice.calculated_price.calculated_amount)86 }, [selectedVariantPrice])87 88 const isSale = useMemo(() => {89 if (!selectedVariantPrice) {90 return false91 }92 93 // @ts-ignore94 return selectedVariantPrice.calculated_price.calculated_price.price_list_type === "sale"95 }, [selectedVariantPrice])96 97 const originalPrice = useMemo(() => {98 if (!isSale) {99 return100 }101 102 // @ts-ignore103 return formatPrice(selectedVariantPrice.calculated_price.original_amount)104 }, [isSale, selectedVariantPrice])105 106 return (107 <div>108 {loading && <span>Loading...</span>}109 {product && (110 <>111 <h1>{product.title}</h1>112 {(product.options?.length || 0) > 0 && (113 <ul>114 {product.options!.map((option) => (115 <li key={option.id}>116 {option.title}117 {option.values?.map((optionValue) => (118 <button 119 key={optionValue.id}120 onClick={() => {121 setSelectedOptions((prev) => {122 return {123 ...prev,124 [option.id!]: optionValue.value!,125 }126 })127 }}128 >129 {optionValue.value}130 </button>131 ))}132 </li>133 ))}134 </ul>135 )}136 {selectedVariant && (137 <span>Selected Variant: {selectedVariant.id}</span>138 )}139 {price && (140 <span>141 {!selectedVariant && "From: "}142 {price}143 {isSale && `SALE - Original Price: ${originalPrice}`}144 </span>145 )}146 {product.images?.map((image) => (147 <img src={image.url} key={image.id} />148 ))}149 </>150 )}151 </div>152 )153}
In this example, you:
- Define an
isSale
memo variable that determines whether the chosen variant's price is a sale price. You do that by checking if the value of the variant'scalculated_price.calculated_price.price_list_type
field issale
. - Define an
originalPrice
memo variable that, ifisSale
is enabled, has the formatted original price of the chosen variant. The variant's original price is in thecalculated_price.original_amount
field. - If
isSale
is enabled, show a message to the customer indicating that this product is on sale along with the original price.
Was this page helpful?