Capítulo 90 de 108

Formisch

Core Idea

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).

Key Concepts

  • 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.
  • Dois modos de binding: elementos nativos (Input/Textarea) recebem {...field.props} + value={field.input}; componentes de biblioteca (Select, Checkbox, RadioGroup, Switch) leem field.input e chamam field.onChange(value) manualmente.
  • Funções top-level (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).

Code Examples

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>
  • O que demonstra: Anatomia completa: schema Valibot único, 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>
  • O que demonstra: Array fields via FieldArray + funções insert/remove top-level, com fieldArray.items fornecendo keys estáveis (equivalente ao field.id do RHF).

Reference Tables

OpçãoValorDescrição
validatesubmit (default)Valida no submit
validateblurValida ao perder foco
validateinputValida a cada mudança
validateinitialValida imediatamente na criação do form
revalidateinput (default)Revalida a cada mudança após a primeira validação
revalidateblurRevalida no blur após a primeira
revalidatesubmitRevalida só no submit

Anti-patterns

  • Confundir 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.
  • Esquecer ?? "" no value={field.input}: field.input pode ser undefined inicialmente, quebrando o input controlado.

Key Takeaways

  1. Formisch é schema-first com Valibot (não Zod) e não usa "resolver" — o schema alimenta useForm diretamente.
  2. Toda operação de estado do form é uma função importada com assinatura (form, config), não um método do objeto form (diferente de RHF e TanStack Form).
  3. field.errors já vem como array de strings; é preciso mapear para { message } antes de passar a FieldError.
  4. Dois padrões de binding: nativo ({...field.props} + value={field.input}) vs biblioteca (field.input/field.onChange manual) — mesma distinção conceitual dos outros dois guias, mas API diferente.
  5. Arrays usam FieldArray + insert/remove/move/swap/replace, todas funções top-level com o mesmo padrão (form, config).

Connects To

  • forms (ch087): índice geral.
  • forms/react-hook-form (ch088), forms/tanstack-form (ch089): alternativas com Zod e APIs de método/render-prop diferentes.