Core concepts
Everything in NotForm follows from a handful of ideas. Read this page once and the rest of the documentation becomes predictable.
The schema is the source of truth
You pass an object schema to useForm. NotForm derives three things from it:
- the type of
form.values, which is the schema's input; - the paths that
form.field()andform.arrayField()accept; - the type of the data that
onSubmitreceives, which is the schema's output.
The schema also performs all validation. NotForm has no rule syntax of its own.
Input and output can differ. A schema that turns the text '42' into the number 42 has input { age: string } and output { age: number }. Inputs bind to the values users edit. Outputs are what your code receives. See Schema input and output.
One form instance owns the state
useForm returns a reactive form instance. It owns:
values: the live, editable data;errors: the current list of validation issues;- state flags such as
isDirty,isSubmitting, andisValidating; - actions such as
submit,validate,reset,setError, andsetValue.
You read state directly (form.isDirty, not form.isDirty.value) and pass the instance to <Form :form="form">.
Fields are handles, not copies
form.field('email') does not store a value. It returns a handle whose model reads and writes form.values.email, and whose errors, isTouched, isDirty, and events are scoped to that path.
Fields are cached by path. Calling form.field('email') again, including on every render, returns the same instance with the same id. That is why touched state and pending validation survive re-renders.
Consequence: the optional configuration passed to form.field(path, config) is read once, on the first call for that path.
Field is a convenience component that hands a field to its slot. It is optional. A field is also usable straight from script.
Paths are strings; issue paths are segments
You address values with dot-notation paths: 'email', 'address.city', 'contacts.0.email'. Paths are checked against the schema input.
Validators report where an issue occurred as an array of segments: ['address', 'city']. When you set errors yourself, you write segments:
form.setError({ message: 'Already taken.', path: ['username'] })
An issue with no path applies to the whole form.
Initial values are a baseline
initialValues and initialErrors define the baseline. The form compares against it to compute isDirty, and reset() restores it. reset(values, errors) replaces the baseline, which is how you load saved data into an edit form.
What happens when a user types
- The input updates
field.model, which writes intoform.values. - The input's event (
input,change, orblur) calls the matching handler infield.events. - The handler checks the field's configuration: is this event enabled, or is the field in eager mode with an existing error?
- If a debounce is configured, validation waits for a pause. Blur never waits.
- NotForm validates a snapshot of the whole form with your schema.
- Only the issues at this field's exact path replace the field's errors.
- If the user changed any value while validation was running, the result is discarded as stale and the errors are left alone.
Details are in Validation.
What happens when a user submits
- If a submit is already running, the new one is ignored.
isSubmittingbecomestrueandsubmitCountincreases. Every field now counts as touched.- The whole form is validated.
- If a value changed while validating, validation runs again.
- If the schema reports issues, they replace all errors and
onSubmitis not called. - If it passes, errors are cleared and
onSubmitis awaited with the validated output. isSubmittingreturns tofalse, even ifonSubmitthrows.
Details are in Submission and reset.
Glossary
| Term | Meaning |
|---|---|
| Schema input | The shape of form.values: what users edit, before parsing. |
| Schema output | The shape onSubmit receives: the parsed result. |
| Path | A dot-notation string such as 'address.city'. Used with form.field() and form.setValue(). |
| Segments | A path as an array, such as ['address', 'city']. Used by issues. |
| Issue | One validation problem: a message and an optional path. |
| Field instance | The cached handle returned by form.field(path). |
| Array field | A field for an array, with items and mutation methods. Returned by form.arrayField(path). |
| Item | One entry of an array field. It is a field for its own value and has an index. |
| Baseline | The current initialValues and initialErrors. |
| Touched | A field was blurred, or a submit was attempted. |
| Dirty | Current values differ from the baseline. |
| Stale | A validation result that was made obsolete by a newer run or a value change. It is discarded. |
Next steps
- Binding inputs shows how to connect every kind of control.
- Validation explains how to tune the behavior described above.