Criando formulários com React Hook Form e Zod
Criar formulários é uma das tarefas mais comuns em aplicações web. Porém, validar campos, exibir mensagens de erro e garantir tipagem consistente pode rapidamente se tornar trabalhoso. A combinação entre React Hook Form e Zod resolve esse problema de forma simples, performática e com excelente integração com TypeScript.
O que é React Hook Form?
React Hook Form é uma biblioteca para gerenciamento de formulários focada em performance e simplicidade.
Ela oferece:
- Validação eficiente;
- Menor quantidade de re-renderizações;
- Integração com TypeScript;
- Facilidade para trabalhar com formulários complexos.
O que é Zod?
Zod é uma biblioteca de validação baseada em schemas.
Com ela podemos definir regras de negócio e ainda obter tipagem automática no TypeScript.
Exemplo:
import { z } from "zod";
const userSchema = z.object({
name: z.string(),
email: z.email(),
age: z.number()
});
Dessa forma, a validação e os tipos ficam centralizados em um único lugar.
Instalando as dependências
npm install react-hook-form zod @hookform/resolvers
Criando o schema
Vamos começar definindo as regras do formulário.
import { z } from "zod";
export const registerSchema = z.object({
name: z
.string()
.min(3, "Nome deve possuir pelo menos 3 caracteres"),
email: z
.email("E-mail inválido"),
password: z
.string()
.min(6, "Senha deve possuir no mínimo 6 caracteres")
});
Agora podemos obter os tipos automaticamente:
export type RegisterFormData =
z.infer<typeof registerSchema>;
Configurando o React Hook Form
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
const {
register,
handleSubmit,
formState: { errors }
} = useForm<RegisterFormData>({
resolver: zodResolver(registerSchema)
});
Com isso, todas as validações serão feitas pelo Zod.
Criando o formulário
<form onSubmit={handleSubmit(onSubmit)}>
<input
{...register("name")}
placeholder="Nome"
/>
{errors.name && (
<span>{errors.name.message}</span>
)}
<input
{...register("email")}
placeholder="E-mail"
/>
{errors.email && (
<span>{errors.email.message}</span>
)}
<input
type="password"
{...register("password")}
placeholder="Senha"
/>
{errors.password && (
<span>{errors.password.message}</span>
)}
<button type="submit">
Salvar
</button>
</form>
As mensagens de erro são exibidas automaticamente.
Enviando os dados
function onSubmit(data: RegisterFormData) {
console.log(data);
}
Como o tipo é inferido pelo Zod, o objeto recebido já possui tipagem completa.
Validações mais avançadas
Campos opcionais
const schema = z.object({
phone: z.string().optional()
});
Valor mínimo
age: z.number().min(18)
email: z.email()
URL
website: z.url()
Enum
status: z.enum([
"active",
"inactive"
])
Confirmando senha
Uma validação bastante comum:
const schema = z
.object({
password: z.string(),
confirmPassword: z.string()
})
.refine(
data => data.password === data.confirmPassword,
{
path: ["confirmPassword"],
message: "As senhas não coincidem"
}
);
Trabalhando com valores padrão
useForm({
resolver: zodResolver(schema),
defaultValues: {
name: "Diogo",
email: ""
}
});
Isso é útil em formulários de edição.
Resetando o formulário
const { reset } = useForm();
reset();
Ou:
reset({
name: "",
email: ""
});
Utilizando Controller
Quando trabalhamos com componentes externos, como Material UI ou shadcn/ui, podemos utilizar o Controller.
<Controller
control={control}
name="name"
render={({ field }) => (
<Input {...field} />
)}
/>
Estrutura recomendada
Uma organização comum:
forms
│
├── schemas
│ ├── userSchema.ts
│ ├── loginSchema.ts
│
├── components
│ ├── FormInput.tsx
│
└── pages
Separar schemas dos componentes facilita a manutenção.
Boas práticas
Centralize os schemas
Evite criar validações diretamente nos componentes.
Utilize z.infer
type FormData = z.infer<typeof schema>;
Isso evita duplicação de tipos.
Crie componentes reutilizáveis
Por exemplo:
- Input;
- Select;
- Textarea.
Exiba mensagens amigáveis
Prefira:
"E-mail inválido"
Ao invés de mensagens genéricas.
Utilize TypeScript
A integração entre RHF e Zod é excelente e reduz muitos erros em tempo de desenvolvimento.
Vantagens
- Excelente performance;
- Menos re-renderizações;
- Validação simples;
- Integração com TypeScript;
- Código mais limpo;
- Fácil manutenção.
Quando utilizar?
Essa combinação é ideal para:
- Sistemas administrativos;
- Formulários complexos;
- Aplicações React e Next.js;
- Projetos com TypeScript;
- Aplicações que exigem validação robusta.
Conclusão
React Hook Form e Zod formam uma das melhores combinações para criação de formulários em aplicações React. Com poucas linhas é possível obter validação, tipagem e uma excelente experiência para o usuário.
Se você trabalha com React e TypeScript, vale a pena considerar essa abordagem nos próximos projetos.
Saiba mais
- React Hook Form Documentation
https://react-hook-form.com/ - Zod Documentation
https://zod.dev/ - React Hook Form Resolvers
https://github.com/react-hook-form/resolvers - Controller API
https://react-hook-form.com/docs/usecontroller/controller - TypeScript Support
https://react-hook-form.com/ts - Zod Error Handling
https://zod.dev/error-customization