Learning Resources

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:

Code
const isSale = selectedVariantPrice.calculated_price.calculated_price.price_list_type === "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:

Code
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.

TipLearn more about the formatPrice function in this guide

Full React Example#

For example, in a React-based storefront:

NoteThe example only passes the region_id query parameter for pricing. Learn how to store and retrieve the customer's region in the Regions guides .
Code
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,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's calculated_price.calculated_price.price_list_type field is sale.
  • Define an originalPrice memo variable that, if isSale is enabled, has the formatted original price of the chosen variant. The variant's original price is in the calculated_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?
Edit this page