react-data-form

Configuration & i18n

Ports, form-wide defaults, locales, currency and translation.

The library assumes no backend, no router and no visual identity. Everything that would be an assumption goes through a port, injected once at startup — and every port has a default, so a form works with no configuration at all.

configurePorts#

setup.ts
import { configurePorts } from "react-data-form"
import { fr } from "date-fns/locale"

configurePorts({
  // How the date fields parse and format.
  dateLocale: fr,
  // What Intl uses for dates and amounts.
  intlLocale: "fr-FR",
  currency: "EUR",

  components: {
    // How an IRI is rendered once chosen in a dropdown.
    iriLabel: ({ iri }) => <ResourceName iri={iri} />,
    // Brand mark at the centre of the loader.
    logo: MyLogo,
  },
})
NameTypeDefaultDescription
dateLocaleLocale (date-fns)US EnglishParses and formats every date field, and decides the first day of the week.
intlLocalestring (BCP 47)the runtime'sWhat Intl formats dates, times and amounts with.
currencystring (ISO 4217)"EUR"How PriceInputController renders an amount. The stored value stays an integer of the smallest unit.
components.iriLabelFC<{ iri?, property? }>the raw identifierTurns /api/authors/4 into a name in a dropdown. Configure it once and every choice field in the application reads.
components.logoFCnothingBrand mark shown at the centre of the loader.

It merges

Calling configurePorts twice merges rather than replaces, down to individual components — so a test can override one port without restating the rest.

setFormConfig#

Ports are about the environment; setFormConfig is about the forms themselves. It sets the description every form starts from, so a default applied here needs no repetition.

setup.ts
import { setFormConfig } from "react-data-form"

setFormConfig({
  defaultForm: {
    label: {
      success: "Saved",
      error: "Some fields need another look",
    },
    components: {
      // Every form in the application, unless it says otherwise.
      formSubmitAction: MySubmitBar,
    },
  },
})

It deep-merges into the existing configuration, and the shipped defaults already name the standard components — replacing one leaves the others in place.

Translation#

Every label, description and button caption passes through react-mini-i18n, so your application and the library translate from one dictionary.

import { setTranslation, Trans, translate } from "react-mini-i18n"

setTranslation({
  "First name": "Prénom",
  "Save": "Enregistrer",
})

// Labels go through it automatically; your own components can too.
<Trans>First name</Trans>
translate("Save")
  • Keys are the source strings, so an untranslated label falls back to itself rather than to a missing-key marker.
  • The dictionary is a module-level singleton, which is why the package is a peer dependency — see Installation.

Server rendering#

Both the ports and the dictionary are process-level singletons, so a server-rendered application has to configure them on the server as well as in the browser. Do it at module scope, in a file both entry points import.

// A singleton is per-process, so configure once, at module scope, on both
// sides — a server render that skipped it would emit US dates the browser then
// disagrees with.
configurePorts({ intlLocale: "fr-FR", currency: "EUR" })

This site does exactly that

It runs on TanStack Start, configures both libraries once from a module imported by the root route, and renders the prose on the server while mounting the live examples in the browser.