Founding price: $35, then $79.99 after 23:59 UTC on 4 November, then $79.99
Get ProA dependency-free form state store with typed field subscriptions, validation on change, blur and submit, schema support, nested paths and array helpers.
This item has no props to tune. Use the lens above to change theme, density, direction and more.
import { Field, FieldError, FieldLabel } from "@/components/ui/field"
import { FormSubscribe, useFormStore } from "@/components/ui/form-store"
import { Input } from "@/components/ui/input"const form = useFormStore({
defaultValues: { email: "" },
validators: { submit: schema }, // anything with safeParse, or a function
onSubmit: async ({ value }) => save(value),
})
<form onSubmit={form.handleSubmit} noValidate>
<form.Field<string> name="email">
{(field) => (
<Field data-invalid={field.invalid}>
<FieldLabel htmlFor="email">Email</FieldLabel>
<Input id="email" {...field.inputProps} />
<FieldError errors={field.errors} />
</Field>
)}
</form.Field>
</form>Validators run on change, blur and submit, as a function that returns { path: ["message"] } or as a zod-style schema. A field shows its errors after it was touched or a submit was tried. Paths use dots for nesting (address.city, phones.0.number).
| Export | Description |
|---|---|
useFormStore(options) | Creates the store. form.Field, form.handleSubmit, form.reset, form.setValue, form.setErrors, form.pushValue, form.removeValue, form.moveValue, form.insertValue. |
FormSubscribe | Render a slice of the state (submitting, isDirty, errors) without re-rendering the form. |
useFormSelector(form, selector) | The same as a hook. |
getIn, setIn, parsePath, deepEqual | Path helpers, useful in your own validators. |
| Option | Type | Description |
|---|---|---|
defaultValues | T | Initial values. isDirty compares against them; reset() returns to them. |
validators | { change?, blur?, submit? } | Each is a function (values) => errors (sync or async) or a schema with safeParse. |
onSubmit | ({ value }) => void | Promise<void> | Runs when every validator passes. A thrown error is stored as submitError. |
field (the argument of the form.Field render function): value, errors (shown, as { message } objects for FieldError), messages (the same as strings), allErrors, touched, dirty, invalid, setValue, onChange (event or value), onBlur, inputProps.
Field, FieldLabel (with htmlFor) and FieldError (a role="alert" region). An invalid field gets aria-invalid through inputProps.aria-invalid="true" inside the form.The component ships with a Testing Library test (form-store.test.tsx) that covers: its main behaviour, the keyboard path and the error or empty states.