Aprenda / Frontend — Tailwind & Next.js

FundamentosAula 9 de 2018 min0% da trilha

Formulários com React Hook Form + Zod

Um formulário de verdade tem validação, mensagem de erro e estado de envio — não só um onSubmit.

Um <form onSubmit> cru não valida nada, não mostra erro por campo, e reconstrói o objeto do zero a cada tecla se você tentar controlar tudo com useState. React Hook Form resolve o estado do formulário; Zod resolve a validação — e os dois se encaixam através de um "resolver".

1. O schema — a validação como dado, não como if/else

schema.ts
import { z } from "zod";

export const loginSchema = z.object({
  email: z.string().email("E-mail inválido"),
  senha: z.string().min(8, "Mínimo de 8 caracteres"),
});

export type LoginForm = z.infer<typeof loginSchema>;

z.infer gera o tipo TypeScript a partir do schema — o tipo e a validação nunca ficam dessincronizados, porque são a mesma fonte.

2. O formulário — RHF cuida do estado, Zod valida

login-form.tsx
"use client";

import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { loginSchema, type LoginForm } from "./schema";

export function LoginForm() {
  const { register, handleSubmit, formState: { errors, isSubmitting } } = useForm<LoginForm>({
    resolver: zodResolver(loginSchema),
  });

  async function onSubmit(data: LoginForm) {
    await fetch("/api/login", { method: "POST", body: JSON.stringify(data) });
  }

  return (
    <form onSubmit={handleSubmit(onSubmit)} className="flex flex-col gap-3 max-w-sm">
      <input {...register("email")} placeholder="E-mail" className="border rounded-lg px-3 py-2" />
      {errors.email && <span className="text-sm text-red-600">{errors.email.message}</span>}

      <input {...register("senha")} type="password" placeholder="Senha" className="border rounded-lg px-3 py-2" />
      {errors.senha && <span className="text-sm text-red-600">{errors.senha.message}</span>}

      <button disabled={isSubmitting} className="bg-blue-600 text-white rounded-lg px-4 py-2 disabled:opacity-50">
        {isSubmitting ? "Entrando..." : "Entrar"}
      </button>
    </form>
  );
}

register("email") conecta o input ao RHF sem precisar de value/onChange manuais; errors.email.message já vem com a mensagem do próprio schema Zod; isSubmitting vem de graça, sem um useState extra pra loading.

Exercício

Adicione ao loginSchema um campo opcional lembrarDeMim do tipo booleano, e um checkbox correspondente no formulário.

Quiz rápido

O que z.infer<typeof schema> resolve?
Pra que serve register("campo") do React Hook Form?