Tutorial — Formulários
Antes do usuário entrar no app, ele precisa se cadastrar — e o cadastro precisa
ser validado: e-mail com formato certo, senha com tamanho mínimo, telefone
preenchido. Nesta página você monta um formulário de cadastro com useZodForm
(validação por schema), FormField (cola a validação aos componentes) e um campo
mascarado de telefone BR (PhoneInput), e por fim envia os dados pro cliente HTTP.
A regra de ouro: o schema zod é a fonte de verdade. O tipo dos valores é inferido dele — você não declara o tipo duas vezes.
Passo 1 — O schema com zod
zod é dependência direta do SDK, já instalada. Descreva o formato do
formulário com z.object:
// src/schemas/signup.ts
import { z } from "zod";
export const signupSchema = z.object({
name: z.string().min(2, "Informe seu nome"),
email: z.string().email("E-mail inválido"),
phone: z.string().min(14, "Telefone incompleto"),
password: z.string().min(8, "Mínimo de 8 caracteres"),
});
export type SignupValues = z.infer<typeof signupSchema>;
Cada .min(...) / .email(...) carrega a mensagem de erro que aparece no
campo. O z.infer deriva o tipo SignupValues — a única definição de tipo que
você precisa.
Passo 2 — useZodForm, o tudo-em-um
useZodForm embrulha o useForm do react-hook-form já ligado ao seu schema, sem
precisar de pacote de resolver extra. Você passa o schema e os valores iniciais:
// src/pages/Signup.tsx (esqueleto)
import { useZodForm } from "tempest-react-sdk";
import { signupSchema, type SignupValues } from "@/schemas/signup";
export function Signup() {
const form = useZodForm(signupSchema, {
defaultValues: { name: "", email: "", phone: "", password: "" },
});
// form.register, form.handleSubmit, form.formState, form.control ...
}
O form devolvido é o objeto padrão do react-hook-form — register,
handleSubmit, control, formState.errors, formState.isSubmitting.
Passo 3 — FormField + FormProvider
Ligar cada campo à validação na mão dá trabalho. O FormField é a cola entre o
react-hook-form e os componentes controlados do SDK: ele injeta value,
onChange, label, error e o estado de erro no componente filho
automaticamente. Para o FormField achar o control, envolva tudo num
FormProvider (também re-exportado pelo SDK).
// src/pages/Signup.tsx
import {
useZodForm,
FormProvider,
Form,
FormField,
FormActions,
Input,
Button,
PhoneInput,
} from "tempest-react-sdk";
import { signupSchema, type SignupValues } from "@/schemas/signup";
export function Signup() {
const form = useZodForm(signupSchema, {
defaultValues: { name: "", email: "", phone: "", password: "" },
});
function onSubmit(values: SignupValues) {
console.log("válido!", values);
}
return (
<FormProvider {...form}>
<Form layout="stack" gap={4} onSubmit={form.handleSubmit(onSubmit)}>
<FormField name="name" label="Nome" required>
<Input />
</FormField>
<FormField name="email" label="E-mail" required>
<Input type="email" />
</FormField>
<FormField name="phone" label="Telefone" required>
<PhoneInput />
</FormField>
<FormField name="password" label="Senha" required>
<Input type="password" />
</FormField>
<FormActions align="end">
<Button type="submit" loading={form.formState.isSubmitting}>
Criar conta
</Button>
</FormActions>
</Form>
</FormProvider>
);
}
Peça por peça:
<FormProvider {...form}>publica oformno contexto — é o que oFormFieldlê pra achar ocontrol.<Form layout="stack" gap={4}>é só layout (flex coluna, gap de 16px) — ele não se acopla a nenhuma form library.- Cada
<FormField name="..." label="...">casa onamecom uma chave do schema e injetavalue/onChange/errorno filho. Os erros do zod aparecem automaticamente abaixo do campo. form.handleSubmit(onSubmit)só chama oonSubmitquando tudo valida.
O name precisa bater com uma chave do schema
<FormField name="email"> exige uma chave email no signupSchema. Como
useZodForm é tipado pelo schema, escrever name="emial" vira erro de
compilação — você pega o engano antes de rodar. ✅
Passo 4 — O campo mascarado de telefone
Você não escreveu nenhuma lógica de máscara — PhoneInput é um dos campos BR
prontos do SDK. Ele formata o que o usuário digita (11999998888 →
(11) 99999-8888) e repassa o valor mascarado pro form. Outros disponíveis:
| Componente | Formata |
|---|---|
PhoneInput |
telefone (11) 99999-8888 |
CPFInput |
CPF 123.456.789-09 |
CNPJInput |
CNPJ 12.345.678/0001-90 |
CEPInput |
CEP 01310-100 |
MoneyInput |
moeda R$ 1.234,56 |
Todos funcionam dentro de um FormField exatamente como o PhoneInput acima —
basta trocar o filho.
A máscara afeta o valor validado
O phone chega ao schema já mascarado ((11) 99999-8888, 15 caracteres).
Por isso o .min(14) do schema considera os caracteres da máscara. Se o seu
backend quiser só dígitos, desmascare no onSubmit antes de enviar (o SDK
exporta unmask pra isso).
Passo 5 — Enviando pro backend
Agora ligue o onSubmit ao cliente HTTP que você criou na página de
busca de dados. Em vez de console.log, faça o POST e, no
sucesso, navegue pra tela de login:
// src/pages/Signup.tsx (apenas o onSubmit)
import { useNavigate } from "tempest-react-sdk";
import { api } from "@/lib/api";
import type { SignupValues } from "@/schemas/signup";
// dentro do componente:
const navigate = useNavigate();
async function onSubmit(values: SignupValues) {
await api.post("/auth/signup", { body: values });
navigate("/login");
}
Repare que form.formState.isSubmitting (passado em loading={...} no botão)
fica true enquanto a Promise do onSubmit não resolve — o botão mostra o
estado de carregamento sozinho, porque o react-hook-form acompanha a async
function.
Erros de validação x erros de rede
O zod cuida da validação antes do envio: campos inválidos nem chegam ao
onSubmit. Já um erro de rede (o POST falhar) é um throw do
api.post — trate com try/catch no onSubmit se quiser mostrar uma
mensagem de "falha ao cadastrar".
Recap
- O schema zod é a fonte de verdade: mensagens de erro moram nele e o tipo
sai de
z.infer. ✅ useZodForm(schema, { defaultValues })embrulha o react-hook-form já ligado ao zod — sem pacote de resolver extra.<FormProvider {...form}>publica o form no contexto; cada<FormField name="..." label="...">cola validação e erros ao componente filho. Onameé checado contra o schema em tempo de compilação.<Form layout="stack">cuida só do layout — não se acopla a form library.- Campos BR mascarados (
PhoneInput,CPFInput,CNPJInput,CEPInput,MoneyInput) entram como filhos doFormFieldsem código de máscara seu. form.handleSubmit(onSubmit)só chama oonSubmitquando tudo valida; lá dentro você chamaapi.post(...)e navega no sucesso.
➡️ Próxima página: Fluxo de autenticação — juntando store, guard e cliente HTTP