Example: Show Product Variant's Price

In this document, you'll learn how to display a product variant's price with a full React example.

Display Selected Variant Price#

Once the customer selects a variant, use its calculated_price.calculated_amount property to display its price:

Code
1const price = formatPrice(2  selectedVariantPrice.calculated_price.calculated_amount3)

You'll learn about the formatPrice function in the next section.


Price Formatting#

To format the price, use JavaScript's NumberFormat utility. You pass it the amount and the currency code (which you retrieve from the selected region):

Code
1const formatPrice = (amount: number): string => {2  return new Intl.NumberFormat("en-US", {3    style: "currency",4    currency: region.currency_code,5  })6  .format(amount)7}

Full React Example#

The following React-based storefront example retrieves the product's price based on the selected variant:

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 || "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  return (89    <div>90      {loading && <span>Loading...</span>}91      {product && (92        <>93          <h1>{product.title}</h1>94          {(product.options?.length || 0) > 0 && (95            <ul>96              {product.options!.map((option) => (97                <li key={option.id}>98                  {option.title}99                  {option.values?.map((optionValue) => (100                    <button 101                      key={optionValue.id}102                      onClick={() => {103                        setSelectedOptions((prev) => {104                          return {105                            ...prev,106                            [option.id!]: optionValue.value!,107                          }108                        })109                      }}110                    >111                      {optionValue.value}112                    </button>113                  ))}114                </li>115              ))}116            </ul>117          )}118          {selectedVariant && (119            <span>Selected Variant: {selectedVariant.id}</span>120          )}121          {price && (122            <span>123              {!selectedVariant && "From: "}124              {price}125            </span>126          )}127          {product.images?.map((image) => (128            <img src={image.url} key={image.id} />129          ))}130        </>131      )}132    </div>133  )134}

In the example above, you:

  • Use the useRegion hook defined in the previous Region React Context guide.
  • Pass the pricing query parameters to the request retrieving the product. This retrieves for every variant a new calculated_price field holding details about the variant's price.
  • Choose the variant to show its price:
    • If there's a selected variant, choose it.
    • If there isn't a selected variant, retrieve and choose the variant with the cheapest price.
  • Format the price based on the chosen variant in the previous step. The variant's calculated_price.calculated_amount field is used.
  • Display the formatted price to the customer. If there isn't a select variant, show a From label to indicate that the price shown is the cheapest.
Was this page helpful?
Edit this page