Concepts (read once) Mado 0.15.3
Forms
useForm() adds reactive state to the browser's form and constraint-validation
model. HTML remains the source of truth for required, type, min, max,
minlength, maxlength and pattern.
import { html, useForm } from "@madojs/mado";
const form = useForm({
initial: { email: "", age: "" as number | "", newsletter: false },
validate: async (values, { signal }) => {
const available = await api.emailAvailable(values.email, { signal });
return available ? null : { email: "Email is already registered" };
},
});
html`
<form @submit=${form.onSubmit(async (values, event) => {
await api.save(values, new FormData(event.currentTarget as HTMLFormElement));
})}>
<label>
Email
<input name="email" type="email" required
@input=${form.onInput} @blur=${form.onBlur} />
</label>
${() => form.touched().email && form.errors().email
? html`<small role="alert">${form.errors().email}</small>`
: null}
<input name="age" type="number" min="18" max="120" @input=${form.onInput} />
<label><input name="newsletter" type="checkbox" @input=${form.onInput} /> Subscribe</label>
<button type="submit" ?disabled=${() => form.submitting() || form.validating()}>
Save
</button>
</form>
`;
API
useForm<T>({ initial, validate? }) returns:
| Member | Meaning |
|---|---|
values() |
current typed values |
errors() |
native and custom errors by field; $form is available for form-wide errors |
touched() |
fields that have blurred or participated in submit |
dirty() |
whether values differ from the current initial values |
isValid() |
whether the merged error map is empty |
submitting() / validating() |
current async activity |
setField(name, value) |
update one field programmatically |
validate(form?) |
run native and custom validation; returns Promise<boolean> |
reset(nextInitial?) |
abort validation, clear state and reset native controls |
onInput, onBlur, onSubmit(handler) |
DOM event handlers |
Custom validation receives { signal, form }. Starting another validation or
calling reset() aborts the previous run. Treat abort as normal cancellation.
Native controls
- Checkbox groups produce an array when the initial field value is an array; a single checkbox produces a boolean.
- Radio groups produce the checked value.
<select multiple>produces a string array.- Number/range inputs produce a number, or
""when empty. - File inputs produce
File | null, orFile[]whenmultipleis present. - The submit callback receives the native
SubmitEvent; constructFormDatafrom its form when the backend expects multipart or repeated fields.
Use a native <button type="submit">. A button inside another element's
Shadow DOM is not a submit control for the outer form and keyboard submission
will not discover it.
Mutations
const save = mutation((values: Profile) => api.save(values), {
invalidates: ["/api/profile"],
});
const form = useForm({ initial: { displayName: "" } });
html`<form @submit=${form.onSubmit((values) => save.run(values))}>
<input name="displayName" required minlength="2" @input=${form.onInput} />
<button type="submit" ?disabled=${() => form.submitting()}>Save</button>
</form>`;
Write failures belong to the mutation; field and cross-field validation belong to the form. See the 0.12 → 0.13 migration guide for the removed schema and field-array APIs.