Formulários
Angular tem dois sistemas robustos de formulários embutidos. No Next.js, você tem várias opções — desde Server Actions (sem JavaScript no cliente) até React Hook Form para formulários complexos com validação client-side.
Reactive Forms vs. Server Actions
A abordagem recomendada no Next.js para formulários simples são as Server Actions — funções que rodam no servidor diretamente chamadas pelo atributo action do form:
// Reactive Forms — Angular
@Component({
template: `
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<input formControlName="email" type="email" />
<span *ngIf="form.get('email')?.errors?.['required']">
E-mail obrigatório
</span>
<span *ngIf="form.get('email')?.errors?.['email']">
E-mail inválido
</span>
<input formControlName="password" type="password" />
<span *ngIf="form.get('password')?.errors?.['minlength']">
Mínimo 8 caracteres
</span>
<button type="submit" [disabled]="form.invalid">
Entrar
</button>
</form>
`,
})
export class LoginComponent {
form = this.fb.group({
email: ['', [Validators.required, Validators.email]],
password: ['', [
Validators.required,
Validators.minLength(8),
]],
});
constructor(private fb: FormBuilder) {}
onSubmit() {
if (this.form.valid) {
console.log(this.form.value);
}
}
}// Server Action + formulário HTML — Next.js
// Roda no servidor: sem JS no cliente necessário!
async function login(formData: FormData) {
"use server"; // esta função roda no servidor
const email = formData.get("email") as string;
const password = formData.get("password") as string;
// Validação no servidor
if (!email || !password) {
throw new Error("Campos obrigatórios");
}
// Lógica de autenticação diretamente aqui
await signIn({ email, password });
redirect("/dashboard");
}
// Server Component — sem useState, sem 'use client'
export default function LoginPage() {
return (
<form action={login}>
<input name="email" type="email" required />
<input name="password" type="password" minLength={8} required />
<button type="submit">Entrar</button>
</form>
);
}Template-driven vs. Controlled Inputs
Os formulários template-driven do Angular têm um equivalente em React usando inputs controlados (controlled inputs), onde você guarda o valor em estado com useState:
Para formulários com validação client-side
<!-- Template-driven Forms — Angular -->
<form #loginForm="ngForm" (ngSubmit)="onSubmit(loginForm)">
<input
name="email"
type="email"
ngModel
required
email
#emailField="ngModel"
/>
<span *ngIf="emailField.invalid && emailField.touched">
E-mail inválido
</span>
<input
name="password"
type="password"
ngModel
required
minlength="8"
/>
<button type="submit" [disabled]="loginForm.invalid">
Entrar
</button>
</form>"use client";
import { useState } from "react";
export default function LoginForm() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [errors, setErrors] = useState<Record<string, string>>({});
function validate() {
const errs: Record<string, string> = {};
if (!email) errs.email = "E-mail obrigatório";
else if (!/S+@S+/.test(email)) errs.email = "E-mail inválido";
if (password.length < 8) errs.password = "Mínimo 8 caracteres";
return errs;
}
function onSubmit(e: React.FormEvent) {
e.preventDefault();
const errs = validate();
if (Object.keys(errs).length > 0) {
setErrors(errs);
return;
}
console.log({ email, password });
}
return (
<form onSubmit={onSubmit}>
<input value={email} onChange={(e) => setEmail(e.target.value)} />
{errors.email && <span>{errors.email}</span>}
<input value={password} onChange={(e) => setPassword(e.target.value)} />
{errors.password && <span>{errors.password}</span>}
<button type="submit">Entrar</button>
</form>
);
}React Hook Form — equivalente ao FormBuilder
Para formulários mais complexos, a biblioteca React Hook Form oferece a experiência mais próxima do Reactive Forms do Angular, com suporte a Zod e Yup para validação de esquema:
"use client";
// React Hook Form — equivale ao FormBuilder do Angular
// npm install react-hook-form
import { useForm } from "react-hook-form";
type FormData = {
email: string;
password: string;
};
export default function LoginForm() {
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm<FormData>();
function onSubmit(data: FormData) {
console.log(data); // { email: '...', password: '...' }
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input
type="email"
{...register("email", {
required: "E-mail obrigatório",
pattern: {
value: /S+@S+/,
message: "E-mail inválido",
},
})}
/>
{errors.email && <span>{errors.email.message}</span>}
<input
type="password"
{...register("password", {
required: "Senha obrigatória",
minLength: { value: 8, message: "Mínimo 8 caracteres" },
})}
/>
{errors.password && <span>{errors.password.message}</span>}
<button type="submit" disabled={isSubmitting}>
Entrar
</button>
</form>
);
}useActionState — feedback em Server Actions
O hook useActionState do React 19 permite gerenciar estado de submissão e mensagens de erro em Server Actions, mantendo a lógica no servidor:
"use client";
import { useActionState } from "react"; // React 19+
async function login(prevState: string | null, formData: FormData) {
"use server";
const email = formData.get("email") as string;
const password = formData.get("password") as string;
if (!email || !password) return "Preencha todos os campos";
const ok = await authenticate(email, password);
if (!ok) return "Credenciais inválidas";
redirect("/dashboard");
return null;
}
export default function LoginForm() {
const [error, formAction, isPending] = useActionState(login, null);
return (
<form action={formAction}>
<input name="email" type="email" required />
<input name="password" type="password" required />
{error && <p className="text-red-500">{error}</p>}
<button type="submit" disabled={isPending}>
{isPending ? "Entrando..." : "Entrar"}
</button>
</form>
);
}Quando usar cada abordagem
| Abordagem | Quando usar | Equivalente Angular |
|---|---|---|
| Server Action + form action | Formulários simples: login, contato, cadastro | Formulário com POST para API |
| useActionState + Server Action | Quando precisa de feedback (loading, erros) com lógica server | Reactive Forms + service |
| Controlled inputs + useState | Formulários com validação instantânea client-side | Template-driven Forms |
| React Hook Form | Formulários complexos, multi-step, validação com Zod/Yup | Reactive Forms + FormBuilder |
Referências: Mutating Data (Server Actions) — Next.js Docs ↗ · React Hook Form ↗