4.1 KiB
useRelativeTime
(no submodules)
Purpose
useRelativeTime(deltaMs, options?) formats a millisecond delta as a
localised relative-time string ("in 5 minutes", "3 hours ago", "il y a
2 jours"). Wraps Intl.RelativeTimeFormat directly with a per-(locale,
style, numeric) cache so the formatter constructor is paid once per
process per option combination. Memoised on (resolvedLocale, deltaMs, options-hash) so a no-op re-render does not reach into the cache or
the formatter.
When the unit option is omitted the hook auto-selects the largest
unit whose absolute value is ≥ 1 (per the universal-core
formatRelativeTime contract). Pass unit explicitly when a fixed
granularity matters — e.g. always render activity timestamps in
minutes within the first hour for consistency with a refresh interval.
Features
- Auto-selects unit (seconds → years) when
unitis omitted. - Forwards
style('long' | 'short' | 'narrow') andnumeric('always' | 'auto'). - Per-(locale, style, numeric) cache of
Intl.RelativeTimeFormatinstances. - Memoised on
(locale, deltaMs, options-hash)— no re-format on identical inputs. - Production no-Provider fallback uses
'en-GB'so the hook always returns a real string. - Re-renders on
languageChangedvia the inheriteduseLocalesubscription. clearUseRelativeTimeCache()exposed for tests / hot-reload scenarios.
Usage: Basic Example
import { useRelativeTime } from "@suaveplan/react-i18n";
export function PostedAt(props: { readonly postedAt: Date }) {
const delta = props.postedAt.getTime() - Date.now();
const formatted = useRelativeTime(delta);
return <time dateTime={props.postedAt.toISOString()}>{formatted}</time>;
}
Usage: Advanced Example
For activity feeds that update once per second, pin the unit to
'seconds' for the first minute and 'minutes' thereafter. The
pattern below shows a row that picks the unit explicitly to avoid
stuttering between auto-selected units near boundary thresholds.
import { useRelativeTime } from "@suaveplan/react-i18n";
export function LiveTimestamp(props: { readonly when: Date }) {
const delta = props.when.getTime() - Date.now();
const abs = Math.abs(delta);
const unit = abs < 60_000 ? "seconds" : "minutes";
const formatted = useRelativeTime(delta, {
style: "short",
numeric: "always",
unit,
});
return <span data-live="true">{formatted}</span>;
}
API Reference
useRelativeTime(deltaMs: number, options?: UseRelativeTimeOptions): string
- Parameters:
deltaMs— delta in milliseconds. Positive = future, negative = past, zero = now.options?—style,numeric,unit. Whenunitis omitted, the hook auto-selects.
- Returns: the formatted relative-time string.
- Throws:
ReactI18nError(REACT_I18N.NO_PROVIDER)outside a<TranslationProvider>in dev mode (inherited fromuseLocale → useI18n).
clearUseRelativeTimeCache(): void
Test-only escape hatch to reset the process-level Intl.RelativeTimeFormat cache.
Implementation Notes
- The auto-select thresholds match the universal-core
formatRelativeTimetable:60 s → minutes,60 min → hours,24 h → days,7 d → weeks,30.4375 d → months(Gregorian average),365.25 d → years(Julian average). This keeps React-side and server-side renders stable across the boundary. - The unit conversion rounds via
Math.roundon the way down. Intentionally — a 30.5-second delta rounds to "in 31 seconds" rather than truncating to "in 30 seconds" which would feel wrong to the user staring at a one-second-resolution clock. - The cache key is
(locale, style, numeric)—unitdoes NOT enter the cache becauseIntl.RelativeTimeFormat.format(value, unit)acceptsunitper call. One formatter handles all units for the same (locale, style, numeric) combination. - 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.