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):

Tip: Refer to the Store Selected Region guide to learn how to store and retrieve the selected region's currency code.
Code
const formatPrice = (amount: number): string => {
  return new Intl.NumberFormat("en-US", {
    style: "currency",
    currency: region.currency_code,
  })
  .format(amount)
}

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
const price = formatPrice(
  selectedVariantPrice.calculated_price.calculated_amount
)

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

Tip: Learn 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
"use client" // include with Next.js 13+

import { useEffect, useMemo, useState } from "react"
import { HttpTypes } from "@medusajs/types"
import { useRegion } from "@/providers/region"
import { sdk } from "@/lib/sdk"

type Props = {
  id: string
}

export default function Product({ id }: Props) {
  const [loading, setLoading] = useState(true)
  const [product, setProduct] = useState<
    HttpTypes.StoreProduct | undefined
  >()
  const [selectedOptions, setSelectedOptions] = useState<Record<string, string>>({})
  const { region } = useRegion()

  useEffect(() => {
    if (!loading) {
      return 
    }

    sdk.store.product.retrieve(id, {
      fields: `*variants.calculated_price`,
      region_id: region.id,
    })
    .then(({ product: dataProduct }) => {
      setProduct(dataProduct)
      setLoading(false)
    })
  }, [loading])

  const selectedVariant = useMemo(() => {
    if (
      !product?.variants ||
      !product.options || 
      Object.keys(selectedOptions).length !== product.options?.length
    ) {
      return
    }

    return product.variants.find((variant) => variant.options?.every(
      (optionValue) => optionValue.value === selectedOptions[optionValue.option_id!]
    ))
  }, [selectedOptions, product])

  const formatPrice = (amount: number): string => {
    return new Intl.NumberFormat("en-US", {
      style: "currency",
      currency: region.currency_code,
    })
    .format(amount)
  }

  const selectedVariantPrice = useMemo(() => {
    if (selectedVariant) {
      return selectedVariant
    }

    return product?.variants?.sort((a: any, b: any) => {
      return (
        a.calculated_price.calculated_amount -
        b.calculated_price.calculated_amount
      )
    })[0]
  }, [selectedVariant, product])

  const price = useMemo(() => {
    if (!selectedVariantPrice) {
      return
    }

    // @ts-ignore
    return formatPrice(selectedVariantPrice.calculated_price.calculated_amount)
  }, [selectedVariantPrice])

  return (
    <div>
      {loading && <span>Loading...</span>}
      {product && (
        <>
          <h1>{product.title}</h1>
          {(product.options?.length || 0) > 0 && (
            <ul>
              {product.options!.map((option) => (
                <li key={option.id}>
                  {option.title}
                  {option.values?.map((optionValue) => (
                    <button 
                      key={optionValue.id}
                      onClick={() => {
                        setSelectedOptions((prev) => {
                          return {
                            ...prev,
                            [option.id!]: optionValue.value!,
                          }
                        })
                      }}
                    >
                      {optionValue.value}
                    </button>
                  ))}
                </li>
              ))}
            </ul>
          )}
          {selectedVariant && (
            <span>Selected Variant: {selectedVariant.id}</span>
          )}
          {price && (
            <span>
              {!selectedVariant && "From: "}
              {price}
            </span>
          )}
          {product.images?.map((image) => (
            <img src={image.url} key={image.id} />
          ))}
        </>
      )}
    </div>
  )
}

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.
Found a problem with this page?Report Issue
Edited Nov 14·Edit this page