Example: Show Product Variant's Price

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

Tip

This guide doesn't take into account sale or tax prices. Refer to the following guides to learn how to show them:

Price Formatting#

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

TipRefer to the Store Selected Region guide to learn how to store and retrieve the selected region's currency code.
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}

You'll use this function to format prices in your storefront, including the selected variant's price.


Display Selected Variant Price#

Once the customer selects a variant (as explained in the Select Product Variants guide), use its calculated_price.calculated_amount property to display its price:

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

The price variable holds the formatted price of the selected variant with the currency of the selected region.

TipLearn about the calculated_price field in the Retrieve Product Variant's Prices section.

Full React Example#

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

Tip
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"6import { sdk } from "@/lib/sdk"7
8type Props = {9  id: string10}11
12export default function Product({ id }: Props) {13  const [loading, setLoading] = useState(true)14  const [product, setProduct] = useState<15    HttpTypes.StoreProduct | undefined16  >()17  const [selectedOptions, setSelectedOptions] = useState<Record<string, string>>({})18  const { region } = useRegion()19
20  useEffect(() => {21    if (!loading) {22      return 23    }24
25    sdk.store.product.retrieve(id, {26      fields: `*variants.calculated_price`,27      region_id: region.id,28    })29    .then(({ product: dataProduct }) => {30      setProduct(dataProduct)31      setLoading(false)32    })33  }, [loading])34
35  const selectedVariant = useMemo(() => {36    if (37      !product?.variants ||38      !product.options || 39      Object.keys(selectedOptions).length !== product.options?.length40    ) {41      return42    }43
44    return product.variants.find((variant) => variant.options?.every(45      (optionValue) => optionValue.value === selectedOptions[optionValue.option_id!]46    ))47  }, [selectedOptions, product])48
49  const formatPrice = (amount: number): string => {50    return new Intl.NumberFormat("en-US", {51      style: "currency",52      currency: region.currency_code,53    })54    .format(amount)55  }56
57  const selectedVariantPrice = useMemo(() => {58    if (selectedVariant) {59      return selectedVariant60    }61
62    return product?.variants?.sort((a: any, b: any) => {63      return (64        a.calculated_price.calculated_amount -65        b.calculated_price.calculated_amount66      )67    })[0]68  }, [selectedVariant, product])69
70  const price = useMemo(() => {71    if (!selectedVariantPrice) {72      return73    }74
75    // @ts-ignore76    return formatPrice(selectedVariantPrice.calculated_price.calculated_amount)77  }, [selectedVariantPrice])78
79  return (80    <div>81      {loading && <span>Loading...</span>}82      {product && (83        <>84          <h1>{product.title}</h1>85          {(product.options?.length || 0) > 0 && (86            <ul>87              {product.options!.map((option) => (88                <li key={option.id}>89                  {option.title}90                  {option.values?.map((optionValue) => (91                    <button 92                      key={optionValue.id}93                      onClick={() => {94                        setSelectedOptions((prev) => {95                          return {96                            ...prev,97                            [option.id!]: optionValue.value!,98                          }99                        })100                      }}101                    >102                      {optionValue.value}103                    </button>104                  ))}105                </li>106              ))}107            </ul>108          )}109          {selectedVariant && (110            <span>Selected Variant: {selectedVariant.id}</span>111          )}112          {price && (113            <span>114              {!selectedVariant && "From: "}115              {price}116            </span>117          )}118          {product.images?.map((image) => (119            <img src={image.url} key={image.id} />120          ))}121        </>122      )}123    </div>124  )125}

In the example above, you:

  • Use the useRegion hook defined in the previous Region React Context guide to retrieve the selected region's currency code. This is necessary to format the variant's price.
  • 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?
Ask Anything
FAQ
What is Medusa?
How can I create a module?
How can I create a data model?
How do I create a workflow?
How can I extend a data model in the Product Module?
Recipes
How do I build a marketplace with Medusa?
How do I build digital products with Medusa?
How do I build subscription-based purchases with Medusa?
What other recipes are available in the Medusa documentation?
Chat is cleared on refresh
Line break