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:

TipFind the implementation of the formatPrice function in this guide.
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.


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 Props = {8  id: string9}10
11export default function Product({ id }: Props) {12  const [loading, setLoading] = useState(true)13  const [product, setProduct] = useState<14    HttpTypes.StoreProduct | undefined15  >()16  const [selectedOptions, setSelectedOptions] = useState<Record<string, string>>({})17  const { region } = useRegion()18
19  useEffect(() => {20    if (!loading) {21      return 22    }23
24    const queryParams = new URLSearchParams({25      fields: `*variants.calculated_price`,26      region_id: region.id,27    })28
29    fetch(`http://localhost:9000/store/products/${id}?${queryParams.toString()}`, {30      credentials: "include",31      headers: {32        "x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",33      },34    })35    .then((res) => res.json())36    .then(({ product: dataProduct }) => {37      setProduct(dataProduct)38      setLoading(false)39    })40  }, [loading])41
42  const selectedVariant = useMemo(() => {43    if (44      !product?.variants ||45      !product.options || 46      Object.keys(selectedOptions).length !== product.options?.length47    ) {48      return49    }50
51    return product.variants.find((variant) => variant.options?.every(52      (optionValue) => optionValue.value === selectedOptions[optionValue.option_id!]53    ))54  }, [selectedOptions, product])55
56  const formatPrice = (amount: number): string => {57    return new Intl.NumberFormat("en-US", {58      style: "currency",59      currency: region.currency_code,60    })61    .format(amount)62  }63
64  const selectedVariantPrice = useMemo(() => {65    if (selectedVariant) {66      return selectedVariant67    }68
69    return product?.variants?.sort((a: any, b: any) => {70      return (71        a.calculated_price.calculated_amount -72        b.calculated_price.calculated_amount73      )74    })[0]75  }, [selectedVariant, product])76
77  const price = useMemo(() => {78    if (!selectedVariantPrice) {79      return80    }81
82    // @ts-ignore83    return formatPrice(selectedVariantPrice.calculated_price.calculated_amount)84  }, [selectedVariantPrice])85
86  const isSale = useMemo(() => {87    if (!selectedVariantPrice) {88      return false89    }90
91    // @ts-ignore92    return selectedVariantPrice.calculated_price.calculated_price.price_list_type === "sale"93  }, [selectedVariantPrice])94
95  const originalPrice = useMemo(() => {96    if (!isSale) {97      return98    }99
100    // @ts-ignore101    return formatPrice(selectedVariantPrice.calculated_price.original_amount)102  }, [isSale, selectedVariantPrice])103
104  return (105    <div>106      {loading && <span>Loading...</span>}107      {product && (108        <>109          <h1>{product.title}</h1>110          {(product.options?.length || 0) > 0 && (111            <ul>112              {product.options!.map((option) => (113                <li key={option.id}>114                  {option.title}115                  {option.values?.map((optionValue) => (116                    <button 117                      key={optionValue.id}118                      onClick={() => {119                        setSelectedOptions((prev) => {120                          return {121                            ...prev,122                            [option.id!]: optionValue.value!,123                          }124                        })125                      }}126                    >127                      {optionValue.value}128                    </button>129                  ))}130                </li>131              ))}132            </ul>133          )}134          {selectedVariant && (135            <span>Selected Variant: {selectedVariant.id}</span>136          )}137          {price && (138            <span>139              {!selectedVariant && "From: "}140              {price}141              {isSale && `SALE - Original Price: ${originalPrice}`}142            </span>143          )}144          {product.images?.map((image) => (145            <img src={image.url} key={image.id} />146          ))}147        </>148      )}149    </div>150  )151}

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