Cómo formatear cantidades monetarias en React Router v7
Muestra precios con símbolos de moneda y separadores
Problema
Mostrar precios en aplicaciones web requiere manejar dos aspectos de localización interrelacionados: la moneda que se utiliza y las convenciones regionales para presentar valores monetarios. Un precio de 1200.50 dólares estadounidenses aparece como "$1,200.50" para usuarios en Estados Unidos, pero como "1 200,50 $US" en Francia. La posición del símbolo de moneda, el separador decimal, la agrupación de miles y el espaciado varían según la configuración regional. Cuando estas convenciones no se respetan, los usuarios pueden malinterpretar las cantidades o cuestionar si el precio es correcto, socavando la confianza en la aplicación.
El desafío se complica cuando una aplicación sirve a múltiples regiones o permite a los usuarios ver precios en diferentes monedas. Codificar cadenas de formato de forma fija o colocar símbolos manualmente crea código frágil que se rompe cuando se añaden nuevas configuraciones regionales o monedas. Sin un enfoque sistemático, mantener visualizaciones de precios consistentes y correctas en toda la aplicación se vuelve propenso a errores.
Solución
Formatea valores monetarios utilizando tanto el código de moneda objetivo como la configuración regional activa del usuario. Este enfoque delega las reglas complejas de presentación de moneda a las API de internacionalización del navegador, que ya codifican las convenciones de formato para cientos de combinaciones de configuración regional y moneda.
Al pasar una cantidad numérica, un código de moneda y la configuración regional actual a una función de formato, el sistema aplica automáticamente el símbolo, los separadores y el diseño correctos. Esto garantiza que los precios siempre aparezcan en un formato familiar para el usuario, independientemente de qué moneda se muestre o en qué región se encuentre el usuario.
Pasos
1. Crea un componente reutilizable de formato de moneda
Crea un componente que use FormattedNumber de react-intl con las props style y currency para formatear valores monetarios.
import { FormattedNumber } from "react-intl";
interface PriceProps {
amount: number;
currency: string;
}
export function Price({ amount, currency }: PriceProps) {
return (
<FormattedNumber value={amount} style="currency" currency={currency} />
);
}
El componente FormattedNumber utiliza la API formatNumber y acepta Intl.NumberFormatOptions. La opción style="currency" indica al formateador que incluya símbolos de moneda, y la prop currency especifica qué moneda mostrar.
2. Usa el componente Price en componentes de ruta
Renderiza precios en cualquier componente de ruta de React Router pasando el valor numérico y el código de moneda.
import { Price } from "~/components/Price";
export default function ProductPage() {
const product = {
name: "Wireless Headphones",
price: 129.99,
currency: "USD",
};
return (
<div>
<h1>{product.name}</h1>
<p>
<Price amount={product.price} currency={product.currency} />
</p>
</div>
);
}
El componente Price formatea automáticamente el importe según la configuración regional configurada en el IntlProvider. Para una configuración regional de EE. UU., esto renderiza "$129.99"; para una configuración regional alemana con la misma moneda USD, renderiza "129,99 $".
3. Formatea moneda de forma imperativa cuando sea necesario
Para escenarios donde necesites la cadena formateada directamente, usa el hook useIntl para acceder al método formatNumber.
import { useIntl } from "react-intl";
export default function CheckoutSummary() {
const intl = useIntl();
const subtotal = 89.99;
const tax = 7.2;
const total = subtotal + tax;
const formattedTotal = intl.formatNumber(total, {
style: "currency",
currency: "EUR",
});
return (
<div>
<h2>Order Summary</h2>
<dl>
<dt>Subtotal</dt>
<dd>
{intl.formatNumber(subtotal, {
style: "currency",
currency: "EUR",
})}
</dd>
<dt>Tax</dt>
<dd>
{intl.formatNumber(tax, {
style: "currency",
currency: "EUR",
})}
</dd>
<dt>Total</dt>
<dd aria-label={`Total: ${formattedTotal}`}>
<strong>{formattedTotal}</strong>
</dd>
</dl>
</div>
);
}
El método formatNumber acepta un valor y un objeto de opciones con FormatNumberOptions. Esto es útil cuando necesitas la cadena formateada para atributos, registro o contextos que no sean JSX.
4. Controla la precisión decimal para monedas de números enteros
Algunas monedas no utilizan unidades fraccionarias. Ajusta el número de decimales para que coincida con las convenciones de la moneda.
import { FormattedNumber } from "react-intl";
interface PriceProps {
amount: number;
currency: string;
}
export function Price({ amount, currency }: PriceProps) {
const isWholeNumberCurrency = currency === "JPY" || currency === "KRW";
return (
<FormattedNumber
value={amount}
style="currency"
currency={currency}
minimumFractionDigits={isWholeNumberCurrency ? 0 : 2}
maximumFractionDigits={isWholeNumberCurrency ? 0 : 2}
/>
);
}
El yen japonés no utiliza una unidad menor, por lo que los importes se muestran sin decimales. Las opciones minimumFractionDigits y maximumFractionDigits anulan el comportamiento decimal predeterminado cuando sea necesario.