Pular para o conteúdo
// visualizar em
salvo em todas as páginas

Formulários

Monte formulários que dizem o que deu errado e onde. A ligação do Field, a validação, o resumo de erros e a receita com react-hook-form.

// você vai aprender

  • O que o Field liga para você, e o que fica com a sua marcação
  • Dois jeitos de validar: no campo, ou com o seu próprio estado
  • Quando usar um ErrorSummary, e para onde vai o foco depois de um envio com erro
  • Como ligar react-hook-form e zod sem perder nada disso

Ligue um campo

O Field liga um rótulo, uma descrição e um erro ao controle que está dentro dele. O FieldLabel vira um <label> do controle, e FieldDescription e FieldError são ligados por aria-describedby, então o leitor de tela lê a dica e o erro quando o controle recebe foco.

cadastro.tsx
import { Field, FieldDescription, FieldError, FieldLabel, Input } from "@camaleo/react";

<Field validationMode="onBlur">
  <FieldLabel>E-mail de trabalho</FieldLabel>
  <Input type="email" autoComplete="email" required />
  <FieldDescription>Respondemos em até um dia útil.</FieldDescription>
  <FieldError match="valueMissing">Informe seu e-mail de trabalho.</FieldError>
</Field>

Input, Textarea, NativeSelect, Select, Combobox, NumberField, Checkbox, Switch, Slider e os campos de data entram no Field em que estão. Agrupe campos relacionados com Fieldset e FieldsetLegend. Para um checkbox ou switch sozinho, um Label em volta basta.

Valide no campo

O Field roda as verificações do navegador (required, type="email", minLength…) e a sua função validate. O FieldError mostra a mensagem; match escolhe qual falha ele informa, e o resultado do validate aparece como está.

O validationMode define quando a verificação acontece. O padrão do Base UI, "onSubmit", foi feito para o componente Form dele, que o Camaleo não embrulha, então defina "onBlur" ou "onChange" no Field, ou deixe uma biblioteca de formulário decidir (abaixo).

minimal · light · #3fa34d

Letras minúsculas, números, pontos e sublinhados.

usuario.tsx
<Field
  validationMode="onChange"
  validate={(value) => (String(value ?? "").length < 3 ? "Use pelo menos 3 caracteres." : null)}
>
  <FieldLabel>Nome de usuário</FieldLabel>
  <Input autoComplete="username" />
  <FieldError />
</Field>

Quando o resultado vem de outro lugar (um servidor, uma biblioteca de formulário), defina invalid no Field e passe o mesmo valor para o match do FieldError, que então mostra o conteúdo dele como está. É assim que a receita abaixo funciona.

DICA

Escreva erros que dizem como resolver: "Informe um e-mail como ana@empresa.com.br", não "Entrada inválida". Cada página de componente tem um par de faz/não faz sobre isso.

Resuma os erros

Quando o formulário tem mais que poucos campos, um envio com erro deve cair num ErrorSummary no topo: um alerta de perigo cujo título diz quantos campos precisam de correção, com um link por erro. Cada link leva o foco ao controle.

checkout.tsx
const summary = useRef<HTMLDivElement>(null);

// Depois de um envio com erro, foque o resumo para ele ser lido primeiro.
useEffect(() => {
  if (failedAttempts > 0) summary.current?.focus();
}, [failedAttempts]);

{errors.length > 0 && (
  <ErrorSummary
    ref={summary}
    tabIndex={-1}
    title={`Corrija ${errors.length} campos para continuar.`}
    errors={errors.map((e) => ({ target: e.id, message: e.message }))}
  />
)}

O target é o id do controle. Gere cada id com useId(), para duas cópias do formulário na mesma página nunca colidirem.

Use react-hook-form e zod

O react-hook-form guarda os valores e roda o schema; o Camaleo mostra o resultado. Envolva cada controle num Controller, passe fieldState.invalid para o Field e espalhe field no controle:

pedir-demo.tsx
const { control, handleSubmit } = useForm<Values>({
  resolver: zodResolver(schema),
  defaultValues: { email: "" },
  // O ErrorSummary recebe o foco, não o primeiro campo inválido.
  shouldFocusError: false,
});

<Controller
  name="email"
  control={control}
  render={({ field, fieldState }) => (
    <Field invalid={fieldState.invalid}>
      <FieldLabel>E-mail de trabalho</FieldLabel>
      <Input {...field} id={ids.email} type="email" autoComplete="email" />
      <FieldError match={fieldState.invalid}>{fieldState.error?.message}</FieldError>
    </Field>
  )}
/>
CUIDADO

Use shouldFocusError: false quando o formulário tiver um ErrorSummary. Senão o react-hook-form leva o foco antes para o primeiro campo inválido, e quem usa leitor de tela nunca ouve o resumo.

O Checkbox informa checked em vez de value, então ligue à mão: checked={field.value}, onCheckedChange={field.onChange} e ref={field.ref}.

Um formulário completo

Tudo acima num formulário só: cinco campos, um schema, o resumo com foco e uma confirmação que recebe o foco quando o formulário some. Envie vazio e depois corrija os campos um a um. Abra a aba Code para ver o arquivo inteiro.

minimal · light · #3fa34d

Respondemos em até um dia útil.

Pelo menos 20 caracteres.

Antes de publicar

  • Todo controle tem um rótulo visível, não só um placeholder
  • Cada erro diz como resolver, no campo e no resumo
  • Depois de um envio com erro, o foco vai para o resumo, e cada link chega ao campo
  • noValidate no <form>, para os balões do navegador não competirem com as suas mensagens
  • Os ids vêm de useId(), e dados pessoais têm autoComplete
Esta página foi útil? · Editar esta página no GitHub