anyfamily-react API reference

Overview

anyfamily-react is the whole any* family as React hooks: every formatter reachable through one shared locale, and relative time that stays fresh without hand-rolled setInterval plumbing.

It adds no formatting of its own. Each hook is the package it is named after, wired to a context and — where it helps — to a tick or a memo.

import { AnyfamilyProvider, useAnywhen, useAnyamount } from 'anyfamily-react'

function App() {
  return (
    <AnyfamilyProvider locale="en">
      <Post publishedAt={post.createdAt} price={1999} />
    </AnyfamilyProvider>
  )
}

function Post({ publishedAt, price }) {
  const when = useAnywhen(publishedAt, { mode: 'relative' })   // "3 hours ago", ticks itself
  const cost = useAnyamount(price, { mode: 'currency', currency: 'EUR' })
  return <p>{cost} — {when}</p>
}

Outside React, or in a server component, reach for anyfamily — the same eight packages behind plain functions.

Install

npm install anyfamily-react
# or
pnpm add anyfamily-react
# or
yarn add anyfamily-react

React is a peer dependency (^18 || ^19); the eight any* packages come along as real dependencies, so there is nothing else to install. ESM and CJS builds with types.

AnyfamilyProvider

Set the locale once, and every hook below it picks it up. The provider holds nothing else — it is a single context around a locale.

import { AnyfamilyProvider } from 'anyfamily-react'

<AnyfamilyProvider locale="de-DE">
  <App />
</AnyfamilyProvider>

// a fallback chain works too
<AnyfamilyProvider locale={['xx-Nope', 'de-DE']}>

It is optional. With no provider each hook falls through to its package's own default, which is whatever the runtime resolves — exactly what calling the function bare would do.

It carries option defaults too — see defaults below.

Nesting works the way any context does: the nearest provider wins, so a subtree can run in another locale.

<AnyfamilyProvider locale="en">
  <Header />                       {/* en */}
  <AnyfamilyProvider locale="ja">
    <Preview />                    {/* ja */}
  </AnyfamilyProvider>
</AnyfamilyProvider>

Hooks

One hook per function in the family. Arguments and options are the package's own — follow the link in each row's description to that package's reference for what the options do.

useAnywhen(date, options?) => string

anywhen as a hook, plus a tick that keeps relative output fresh. options.refresh controls the interval.

useAnyamount(value, options?) => string

anyamount as a hook.

useAnyamountSymbol(currency, options?) => string

anyamount.symbol — the bare currency symbol, for labels and input affixes where the amount renders separately.

useAnymany(items, options?) => string

anymany as a hook.

useAnyaround(code, options?) => string

anyaround as a hook.

useAnylong(input, options?) => string

anylong as a hook.

useAnyplural(count, forms, options?) => string

anyplural as a hook.

useAnyword(text, options?) => string[]

anyword as a hook. Returns an array, memoized on the text and the options' contents.

useAnywordCount(text, options?) => number

anyword.count as a hook.

useAnywordTruncate(text, limit, options?) => string

anyword.truncate as a hook.

useAnylocale(tag?) => AnylocaleInfo

anylocale as a hook. Takes the tag as its argument, not as an option; returns an object, memoized on the tag.

useAnyfamilyLocale() => Locale | undefined

The locale from the nearest provider, for anything the hooks above do not cover.

useAnyfamilyDefaults() => AnyfamilyDefaults | undefined

The option defaults from the nearest provider, for wrapping a hook of your own.

const price   = useAnyamount(1999, { mode: 'currency', currency: 'EUR' })
const symbol  = useAnyamountSymbol('EUR')
const authors = useAnymany(['Ada', 'Grace', 'Alan'])
const country = useAnyaround('DE', { display: 'flag-name' })
const took    = useAnylong({ minutes: 90 })
const label   = useAnyplural(count, { one: 'file', other: 'files' })
const words   = useAnyword(text)
const total   = useAnywordCount(text)
const teaser  = useAnywordTruncate(text, 140)
const info    = useAnylocale()

Plain functions

All eight are re-exported, so formatting outside a hook — in an event handler, inside a useMemo, in a callback handed downward — does not mean adding the underlying package as a second dependency.

import { anywhen, anyword } from 'anyfamily-react'

<button onClick={() => copy(anywhen(post.createdAt, { mode: 'absolute' }))}>
  copy timestamp
</button>

They are the same bindings the hooks call, extras included — anyword.count, anyamount.symbol, anylong.supported. What they do not read is the provider: a plain call takes the locale you pass it, or the runtime's. Reach for useAnyfamilyLocale()when you want the tree's.

const locale = useAnyfamilyLocale()

const onCopy = () => copy(anywhen(post.createdAt, { mode: 'absolute', locale }))

They carry this module's "use client" boundary with them. To format in a server component, import from anyfamily instead.

Defaults

A currency, a style, a tick — the settings that never vary across an app, and get retyped at every call site. The provider takes them once, one slot per hook family.

<AnyfamilyProvider
  locale="de-DE"
  defaults={{
    anyamount: { mode: 'currency', currency: 'EUR' },
    anywhen: { refresh: 30_000 },
    anywordTruncate: { ellipsis: '…' },
  }}
>
  <App />
</AnyfamilyProvider>

useAnyamount(1999)              // "1.999,00 €" — nothing to pass
useAnywordTruncate(body, 140)   // ellipsis already set

The slots are anywhen, anyamount, anyamountSymbol, anymany, anyaround, anylong, anyplural, anyword and anywordTruncate— each taking that hook's own options. anywhen takes refresh along with the rest, so the tick is settable tree-wide.

anyword covers useAnyword and useAnywordCount but deliberately not useAnywordTruncate, which has its own slot: truncate segments by grapheme where the other two segment by word, so sharing one slot would silently move where text gets cut.

A call's own options win, key by key. One rule beyond that:

A different mode replaces the default, it does not layer onto it

The options types are discriminated unions on mode. Merging across two modes would carry the default's mode-specific keys — a currency— into a call that asked for something else, so a call naming another mode takes the default's place. locale is not mode-specific and crosses either way.

// default: { mode: 'currency', currency: 'EUR' }

useAnyamount(1999)                                      // "1.999,00 €"
useAnyamount(1999, { mode: 'currency', currency: 'USD' })  // "1.999,00 $"
useAnyamount(3.2,  { mode: 'unit', unit: 'gigabyte' })     // "3,2 GB" — no
// stray currency; the locale from the provider still applies

Overriding one key of a union-typed default means restating its mode, as above — TypeScript has no partial form of a discriminated union, and the merge rule is the same shape as the type.

useAnyfamilyDefaults() reads the whole object back, for building a hook of your own on top.

Written inline, defaults is a fresh object on every render of whatever holds the provider. The provider keys its context on the contents rather than the identity, so an inline literal does not re-render the tree — no need to hoist it to a constant.

Locale resolution

Four levels, nearest first:

options.locale           // the hook's own — always wins
defaults[slot].locale    // set for one hook family
provider locale          // the nearest AnyfamilyProvider
runtime default          // whatever Intl resolves, when none is set
<AnyfamilyProvider locale="de-DE">
  {/* de-DE, from the provider */}
  useAnyamount(1999, { mode: 'currency', currency: 'EUR' })

  {/* ja-JP — the hook's own locale wins */}
  useAnyamount(1999, { mode: 'currency', currency: 'JPY', locale: 'ja-JP' })
</AnyfamilyProvider>

useAnylocale is the one that differs, because anylocaletakes its tag as an argument rather than as an option. With no argument it reads the provider, and with no provider it resolves the runtime's own locale.

useAnylocale()          // provider locale, else the runtime's
useAnylocale('fa-IR')   // explicit, ignores the provider

Ticking

Relative output goes stale the instant a component stops re-rendering: "3 minutes ago" stays "3 minutes ago" for an hour. useAnywhen re-renders itself on an interval so it does not.

useAnywhen(date, { mode: 'relative' })                  // ticks every 60s
useAnywhen(date, { mode: 'relative', refresh: 10_000 }) // every 10s
useAnywhen(date, { mode: 'relative', refresh: false })  // never

The default tick is skipped where it could not change anything:

In "absolute" mode

A formatted date does not move. No interval is set at all, whatever the mode was when the component mounted.

Past a day old

Output is in days or months by then, and a minute-granularity poll never changes it. A list of last year's posts sets no timers. An explicit refresh always does what it is told.

The tick is a fixed poll rather than a boundary-aligned schedule, so a transition such as "59 seconds ago" to "1 minute ago" can lag up to one interval behind. Pass a smaller refresh where that shows.

Memoized hooks

Most hooks return a string, so referential stability is a non-question. The two that do not — useAnyword (an array) and useAnylocale (an object) — are memoized, and keep their reference until the input actually changes.

const words = useAnyword(text)

useEffect(() => {
  // runs when the segments change, not on every render
}, [words])

The memo is keyed on the options' contents, not their identity, so an inline option object — a fresh reference every render — does not defeat it.

// safe: same contents, same memoized array
const words = useAnyword(text, { by: 'word' })

Recipes

Copy, paste, move on.

// One locale for the app, from the route
<AnyfamilyProvider locale={params.locale}>
  <App />
</AnyfamilyProvider>

// A comment timestamp that stays honest
const posted = useAnywhen(comment.createdAt, { mode: 'relative' })

// Price and symbol rendered apart
const amount = useAnyamount(cents / 100, { mode: 'currency', currency })
const sign   = useAnyamountSymbol(currency)

// A live character counter that counts what people see
const left = 280 - useAnywordCount(draft, { by: 'grapheme' })

// A teaser that never cuts a word — or an emoji — in half
const teaser = useAnywordTruncate(post.body, 140)

// Direction and week layout from the provider's locale
const { direction, weekStart } = useAnylocale()

// The locale itself, for something the hooks do not cover
const locale = useAnyfamilyLocale()

SSR & Next.js

The package is a client module: every export is behind "use client". Hooks hold state and set effects, so a Server Component cannot call them — put the provider in a client boundary and let server components render underneath it as children.

// app/providers.tsx
'use client'
import { AnyfamilyProvider } from 'anyfamily-react'

export function Providers({ locale, children }) {
  return <AnyfamilyProvider locale={locale}>{children}</AnyfamilyProvider>
}

// app/[locale]/layout.tsx — a server component
import { Providers } from '../providers'

export default function Layout({ children, params: { locale } }) {
  return (
    <html lang={locale}>
      <body><Providers locale={locale}>{children}</Providers></body>
    </html>
  )
}

To format on the server, skip this package and call the functions directly — anyfamily is the same eight packages without the React layer.

Two hydration rules, both about determinism rather than about React. Pass the locale explicitly— a hook that falls through to the runtime resolves the server's locale on the server and the browser's in the client, which is a mismatch by construction. And relative time is a clock read: the first client render happens later than the server one, so render an absolute date on the server, or accept that the first tick settles it.

// Deterministic: the tag comes from the route, not from the environment
<AnyfamilyProvider locale={params.locale}>

Support flags

Three of the underlying APIs are newer than the rest, so their packages carry a support flag. It is re-exported here, letting you feature-detect without importing the package alongside.

import {
  anylongSupported,
  anywordSupported,
  anylocaleSupported,
} from 'anyfamily-react'

// Intl.DurationFormat, Intl.Segmenter, Intl Locale Info respectively

These are plain values read at import time, not hooks — call them anywhere, including outside a component. Where a flag is false, the matching hook throws exactly as its function would, so branch before rendering it.

function Counter({ text }) {
  const total = useAnywordCount(text)      // needs Intl.Segmenter
  return <span>{total}</span>
}

function SafeCounter({ text }) {
  // Branch around the component, never around the hook call — hooks must run
  // in the same order on every render.
  if (!anywordSupported) return <span>{text.length}</span>
  return <Counter text={text} />
}

Types

Every option type from the eight packages is re-exported, so a typed wrapper needs one import rather than nine.

import type {
  Locale,
  AnywhenOptions, DateInput,
  AnyamountOptions, AnyamountSymbolOptions,
  AnymanyOptions,
  AnyaroundOptions,
  AnylongOptions, DurationInput,
  AnypluralOptions, Forms,
  AnywordOptions, AnywordTruncateOptions, Granularity,
  AnylocaleInfo, Direction, Weekday,
} from 'anyfamily-react'

UseAnywhenOptions is the one type this package adds: AnywhenOptions plus refresh.

import type { UseAnywhenOptions } from 'anyfamily-react'

function Timestamp({ at, ...rest }: { at: DateInput } & UseAnywhenOptions) {
  return <time>{useAnywhen(at, rest)}</time>
}

Compatibility

React 18 or 19, as a peer dependency. Nothing exotic is used — context, state, effect and memo, all of them present since hooks shipped.

The formatting underneath is native Intl, so browser support is whatever the underlying package says. Three of them may be missing on an older runtime — see anylongSupported, anywordSupported and anylocaleSupported above; the other five have been everywhere for years.

The package tracks the family's versions: a release bumps it in step with the eight, so anyfamily-react and anyfamily at the same version wrap the same code.

Limitations

A few things worth knowing before you ship:

Client components only

The package is a client module — every export sits behind "use client". Hooks need state and effects, so a React Server Component cannot call them. Format on the server with the plain functions from anyfamily instead; they are the same code without the React layer.

The provider is a context, not a store

Changing its locale re-renders every hook underneath it, the same as any other context. Put it high in the tree and change it rarely — a locale that flips on every keystroke re-renders the subtree on every keystroke.

The tick is a poll, not a scheduler

useAnywhen re-renders on a fixed interval rather than on unit boundaries, so a transition like "59 seconds ago" to "1 minute ago" can lag up to one tick behind. Pass an explicit refresh where the alignment matters.

It adds hooks, not behaviour

Every formatting rule, option and edge case lives in the underlying package. When output looks wrong, the answer is in that package's reference — this layer only supplies the locale and re-renders.