Capítulo 90 de 108
Padrão de integração de Formisch (lib de formulário leve, schema-first, type-safe) + Valibot com Field/FieldGroup. API baseada em funções top-level ((form, config)), não em métodos de instância, e o schema Valibot é a única fonte de verdade para validação e tipos (sem "resolver" separado como no RHF).
useForm({ schema, initialInput }): cria o form store a partir de um schema Valibot; tipos de input/output são inferidos direto do schema.<Form of={form} onSubmit={handleSubmit}>: wrapper do <form> nativo; já chama preventDefault(), valida e só invoca onSubmit com dados válidos e tipados.<Field of={form} path={["title"]}>{(field) => ...}</Field> (renomeado FormischField nos exemplos para não colidir com o Field do shadcn): render-prop que expõe field.input, field.errors (array de strings ou null), field.props (name/ref/onChange/onBlur/onFocus prontos), field.onChange.Input/Textarea) recebem {...field.props} + value={field.input}; componentes de biblioteca (Select, Checkbox, RadioGroup, Switch) leem field.input e chamam field.onChange(value) manualmente.(form, config): getInput, setInput, getErrors, setErrors, reset, submit, validate, focus, e para arrays: insert, remove, move, swap, replace — todas seguem a mesma assinatura, primeiro parâmetro é sempre o form store.validate/revalidate (opções do useForm): separam a estratégia da primeira validação da revalidação subsequente.<FieldArray of={form} path={["emails"]}>{(fieldArray) => ...}</FieldArray>: componente dedicado para arrays, expõe fieldArray.items (lista de keys estáveis para usar como key).const FormSchema = v.object({
title: v.pipe(v.string(), v.minLength(5, "..."), v.maxLength(32, "...")),
})
const form = useForm({ schema: FormSchema, initialInput: { title: "" } })
const handleSubmit: SubmitHandler<typeof FormSchema> = (output) => { /* dados validados */ }
<Form of={form} onSubmit={handleSubmit}>
<FormischField of={form} path={["title"]}>
{(field) => (
<Field data-invalid={field.errors !== null}>
<FieldLabel htmlFor="title">Bug Title</FieldLabel>
<Input {...field.props} id="title" value={field.input ?? ""} aria-invalid={field.errors !== null} />
{field.errors && <FieldError errors={field.errors.map((message) => ({ message }))} />}
</Field>
)}
</FormischField>
</Form>
Form cuidando de submit/preventDefault, field.errors como array de strings mapeado para o formato {message} esperado por FieldError.import { insert, remove } from "@formisch/react"
<FieldArray of={form} path={["emails"]}>
{(fieldArray) => (
<FieldGroup className="gap-4">
{fieldArray.items.map((item, index) => (
<FormischField key={item} of={form} path={["emails", index, "address"]}>
{(field) => /* ... */}
</FormischField>
))}
<Button onClick={() => insert(form, { path: ["emails"], initialInput: { address: "" } })} disabled={fieldArray.items.length >= 5}>
Add Email Address
</Button>
</FieldGroup>
)}
</FieldArray>
FieldArray + funções insert/remove top-level, com fieldArray.items fornecendo keys estáveis (equivalente ao field.id do RHF).| Opção | Valor | Descrição |
|---|---|---|
validate | submit (default) | Valida no submit |
validate | blur | Valida ao perder foco |
validate | input | Valida a cada mudança |
validate | initial | Valida imediatamente na criação do form |
revalidate | input (default) | Revalida a cada mudança após a primeira validação |
revalidate | blur | Revalida no blur após a primeira |
revalidate | submit | Revalida só no submit |
Field do Formisch com Field do shadcn: renomear a importação (Field as FormischField) é o padrão recomendado pela própria doc para evitar colisão de nomes.?? "" no value={field.input}: field.input pode ser undefined inicialmente, quebrando o input controlado.useForm diretamente.(form, config), não um método do objeto form (diferente de RHF e TanStack Form).field.errors já vem como array de strings; é preciso mapear para { message } antes de passar a FieldError.{...field.props} + value={field.input}) vs biblioteca (field.input/field.onChange manual) — mesma distinção conceitual dos outros dois guias, mas API diferente.FieldArray + insert/remove/move/swap/replace, todas funções top-level com o mesmo padrão (form, config).