Files
skills/docs/localization/i18n/react-i18n/src/hooks/use-number/use-number.md
2026-05-29 08:20:56 +01:00

3.8 KiB

useNumber

(no submodules)

Purpose

useNumber(value, options?) formats a number for the active locale via Intl.NumberFormat. Memoised on (resolvedLocale, value, options-hash) so a no-op re-render does not reach into Intl.NumberFormat. Subscribes through useLocale so a setLocale('de-DE') re-renders consumers with the German thousand-separator on the same React commit cycle as the bus emission.

The hook keeps a process-level cache of Intl.NumberFormat instances keyed on (locale, options-hash). The first render with a fresh combination pays the constructor cost; subsequent renders (across components, providers, and tests) reuse the cached formatter.

Features

  • Memoised on (locale, value, options-hash) — no re-format on identical inputs.
  • Process-level Intl.NumberFormat cache — the constructor is paid once per (locale, options) combination.
  • Forwards the full Intl.NumberFormatOptions shape (style, notation, useGrouping, signDisplay, minimumFractionDigits, …).
  • Production no-Provider fallback uses 'en-GB' so the hook always returns a real string.
  • Re-renders on languageChanged via the inherited useLocale subscription.
  • clearUseNumberCache() exposed for tests / hot-reload scenarios.

Usage: Basic Example

import { useNumber } from "@suaveplan/react-i18n";

export function PriceTag(props: { readonly amount: number }) {
    const formatted = useNumber(props.amount, {
        style: "decimal",
        minimumFractionDigits: 2,
    });
    return <span>{formatted}</span>;
}

Usage: Advanced Example

The full Intl.NumberFormatOptions shape unlocks compact notation, unit display, sign visibility, and per-locale digit grouping. The pattern below shows a stat tile that switches between standard and compact notation depending on magnitude.

import { useNumber } from "@suaveplan/react-i18n";

export function StatTile(props: {
    readonly value: number;
    readonly unit: string;
}) {
    const compact = props.value >= 10_000;
    const formatted = useNumber(props.value, {
        style: "unit",
        unit: props.unit,
        notation: compact ? "compact" : "standard",
        compactDisplay: "short",
        signDisplay: "exceptZero",
    });
    return <strong>{formatted}</strong>;
}

API Reference

useNumber(value: number, options?: Intl.NumberFormatOptions): string

  • Parameters:
    • value — the numeric value to format.
    • options? — raw Intl.NumberFormatOptions. When omitted, the hook uses the locale's default decimal formatting.
  • Returns: the formatted string.
  • Throws: ReactI18nError(REACT_I18N.NO_PROVIDER) outside a <TranslationProvider> in dev mode (inherited from useLocale → useI18n).

clearUseNumberCache(): void

Test-only escape hatch to reset the process-level Intl.NumberFormat cache. Useful for hot-reload tooling or property-based tests that span thousands of unique option combinations.

Implementation Notes

  • The cache is unbounded by design. Intl.NumberFormat instances are small (a handful of bytes) and the realistic upper bound on unique (locale, options-hash) combinations in a real application is in the dozens, not millions.
  • The memo key is (resolvedLocale, value, optionsHash, options). The explicit options reference is included so consumers who pass the same object across renders share the memo while consumers passing fresh objects with identical contents share via the hash.
  • The process-level cache is shared across every consumer in the bundle. Two unrelated components requesting useNumber(x, { style: 'percent' }) in 'en-US' reach the same cached formatter.
  • The default locale 'en-GB' on the no-Provider fallback matches the broader package convention. This keeps the hook from throwing in production while still producing meaningful output.