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.

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}

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.


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

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