react-data-form
Fields
Every key of a field description, and what each one changes on screen.
The key is the name#
inputs is a record. Its keys are the field names, and they are the keys your onSubmit handler receives — there is no mapping step in between.
inputs: {
// The key is the field's name, and the key of the submitted payload.
emailAddress: { label: "Email", type: "email" },
}
// → onSubmit receives { emailAddress: "…" }Every key of a field#
| Name | Type | Default | Description |
|---|---|---|---|
label | string | ReactNode | null | — | Shown above the control. Passed through the translation dictionary. null renders no label at all. |
description | string | ReactNode | — | Help text under the control. |
placeholder | string | — | Placeholder of the underlying input. |
type | "text" | "email" | "number" | "password" | "checkbox" | "hidden" | … | "text" | Only read by DefaultInputController, which is what a field falls back to. hidden keeps the value out of the page. |
controller | FC<InputControllerInterface> | DefaultInputController | The component that renders the field. |
required | boolean | — | Marks the field as required and passes the attribute to the control. |
readonly | boolean | — | Changes are dropped by useForm, so the value cannot move even if a controller tries. |
defaultValue | Value | ((current) => Value) | — | Applied when the form is built and no data carries the field. |
value | Value | — | The current value. Normally the library's to set, not yours. |
valueOptions | ValueOptionInterface[] | — | Static choices, for any of the select-like controllers. |
getValueOptions | (input?) => Promise<ValueOptionInterface[]> | — | Choices fetched once, when the field first renders. |
onSearch | (query, formContext?) => Promise<ValueOptionInterface[]> | — | Choices fetched per keystroke — the search and autocomplete controllers. |
validator | (value: Value) => Value | never | — | Throws to reject. Zod errors are unpacked into one violation per issue. See Validation. |
violations | ViolationInterface[] | — | Errors currently attached to the field, from a validator or from your API. |
hidden | () => boolean | — | Re-evaluated on render, so a field can appear as another one changes. |
order | number | — | Ascending. Fields without one keep their declaration order. |
groups | string[] | — | Which sections the field belongs to. See Groups and Steps. |
form | FormInterface | — | Renders the field as a nested sub-form. See Nested forms. |
getForm | () => FormInterface | — | The same, resolved lazily — for a recursive or self-referencing shape. |
generatedValue | boolean | — | Excluded from validation and from the table's columns. For values injected by context. |
min / max | number | — | Bounds, read by the number, slider and date controllers. |
components.decorator | FC<BaseDecoratorFormInputInterface> | — | Replaces the wrapper around this one field. |
type, without a controller#
A field with no controller falls back to DefaultInputController, an HTML <input> driven by its type. That covers the ordinary cases with no imports at all.
inputs: {
name: { label: "Name" }, // text
age: { label: "Age", type: "number" },
email: { label: "Email", type: "email" },
secret: { label: "Password", type: "password" },
agreed: { label: "I agree", type: "checkbox" },
internal: { type: "hidden" }, // never rendered
}Plain types, no controllers
Choices#
Every select-like controller reads the same shape — a list of { label, value } — from one of three keys, depending on when the choices are known.
import { valueOptionMapper, valueOptionFromArray } from "react-data-form"
// Written by hand
valueOptions: [
{ label: "Draft", value: "draft" },
{ label: "Published", value: "published" },
]
// From a plain array
valueOptions: valueOptionFromArray(["S", "M", "L"])
// From records, naming the label and value keys
valueOptions: valueOptionMapper(authors, "name", "@id")
// Fetched, once, when the field first renders
getValueOptions: async () => valueOptionMapper(await api.authors(), "name", "@id")
// Fetched on every keystroke — for a search field
onSearch: async (query) => valueOptionMapper(await api.authors({ query }), "name", "@id")| Name | Type | Default | Description |
|---|---|---|---|
labelrequired | string | ReactNode | — | What the reader sees. |
valuerequired | string | number | boolean | — | What ends up in the payload. |
description | string | — | A second line, rendered by the card and radio controllers. |
group | string | — | Groups options under a heading in the dropdown. |
aliases | string[] | — | Extra terms the option matches when searching. |
original | T | — | The record the option was built from — valueOptionMapper keeps it, so a custom item component can read the rest of it. |
Static options
Showing a field conditionally#
hidden is a function, not a boolean, and it is called on every render. That is what lets one field depend on another without any subscription mechanism.
inputs: {
shipping: {
label: "Shipping method",
controller: SelectInputController,
valueOptions: [
{ label: "Pickup", value: "pickup" },
{ label: "Delivery", value: "delivery" },
],
},
address: {
label: "Delivery address",
// Re-evaluated on every render, so it follows the field above.
hidden: () => getFormInputInForm(form, "shipping")?.value !== "delivery",
},
}Hidden is not absent
A hidden field keeps its value and still ships in the payload. To drop it entirely, leave it out of inputs.
Ordering#
Fields render in declaration order until one of them carries an order. Useful when a form is assembled from several sources — a base description plus additions from a plugin.
inputs: {
reference: { label: "Reference", order: 30 },
title: { label: "Title", order: 10 },
notes: { label: "Notes", order: 20 },
}
// Rendered: Title, Notes, Reference.Generated values#
generatedValue: true marks a field whose value comes from the surrounding context rather than the reader. Two things follow:
- validation skips it — nobody typed it, so nobody can fix it;
- the table layout leaves it out of the columns, and the filter bar leaves it out of “clear search”.
Next
The catalogue of controllers is next — the part of the library you will come back to most.