Formulários
Manipule a entrada do usuário com componentes controlados, refs não controladas ou ações de formulário do React 19.
Busque em todas as páginas da documentação
Manipule a entrada do usuário com componentes controlados, refs não controladas ou ações de formulário do React 19.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Cartão de receita de referência rápida - pronto para copiar e colar.
// Input controlado
const [email, setEmail] = useState("");
<input value={email} onChange={e => setEmail(e.target.value)} />
// Input não controlado com ref
const inputRef = useRef<HTMLInputElement>(null);
<input ref={inputRef} defaultValue="" />
// Leia depois: inputRef.current?.value
// Ação de formulário do React 19
async function createUser(formData: FormData) {
const name = formData.get("name") as string;
await saveToDatabase(name);
}
<form action={createUser}>
<input name="name" required />
<button type="submit">Criar</button>
</form>
// useActionState do React 19
const [state, formAction, isPending] = useActionState(submitFn, initialState);Quando usar isso: Sempre que você coletar entrada do usuário - formulários de login, barras de pesquisa, páginas de configurações, assistentes de várias etapas.
"use client";
import { useState, useActionState } from "react";
// --- Formulário Controlado ---
export function ControlledSignup() {
const [form, setForm] = useState({ name: "", email: "", role: "viewer" });
const [submitted, setSubmitted] = useState(false);
function updateField(field: string, value: string) {
setForm(prev => ({ ...prev, [field]: value }));
}
function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setSubmitted(true);
}
if (submitted) {
return (
<div className="rounded bg-green-50 p-4 text-green-800">
Bem-vindo(a), {form.name}! Enviamos uma confirmação para {form.email}.
</div>
);
}
return (
<form onSubmit={handleSubmit} className="max-w-sm space-y-3 rounded border p-4">
<div>
<label htmlFor="name" className="block text-sm font-medium">Nome</label>
<input
id="name"
value={form.name}
onChange={e => updateField("name", e.target.value)}
required
className="w-full rounded border px-3 py-1"
/>
</div>
<div>
<label htmlFor="email" className="block text-sm font-medium">Email</label>
<input
id="email"
type="email"
value={form.email}
onChange={e => updateField("email", e.target.value)}
required
className="w-full rounded border px-3 py-1"
/>
</div>
<div>
<label htmlFor="role" className="block text-sm font-medium">Função</label>
<select
id="role"
value={form.role}
onChange={e => updateField("role", e.target.value)}
className="w-full rounded border px-3 py-1"
>
<option value="viewer">Visualizador</option>
<option value="editor">Editor</option>
<option value="admin">Admin</option>
</select>
</div>
<button type="submit" className="rounded bg-blue-600 px-4 py-2 text-white">
Inscrever-se
</button>
</form>
);
}
// --- Ação de Formulário do React 19 com useActionState ---
interface FormState {
message: string;
error: boolean;
}
async function submitFeedback(
prevState: FormState,
formData: FormData
): Promise<FormState> {
const feedback = formData.get("feedback") as string;
// Simula atraso do servidor
await new Promise(resolve => setTimeout(resolve, 1000));
if (feedback.length < 10) {
return { message: "O feedback deve ter pelo menos 10 caracteres.", error: true };
}
return { message: `Obrigado pelo seu feedback!`, error: false };
}
export function FeedbackForm() {
const [state, formAction, isPending] = useActionState(submitFeedback, {
message: "",
error: false,
});
return (
<form action={formAction} className="max-w-sm space-y-3 rounded border p-4">
<label htmlFor="feedback" className="block text-sm font-medium">
Seu Feedback
</label>
<textarea
id="feedback"
name="feedback"
required
rows={3}
className="w-full rounded border px-3 py-1"
placeholder="Diga-nos o que você pensa..."
/>
{state.message && (
<p className={state.error ? "text-sm text-red-600" : "text-sm text-green-600"}>
{state.message}
</p>
)}
<button
type="submit"
disabled={isPending}
className="rounded bg-blue-600 px-4 py-2 text-white disabled:opacity-50"
>
{isPending ? "Enviando..." : "Enviar Feedback"}
</button>
</form>
);
}O que isso demonstra:
value + onChange para cada inputuseActionState (React 19) gerenciando o estado de envio do formulário, indicador de pendência e validaçãoFormData para ler valores do formulário sem estado controladolabel + htmlForvalue em cada renderizaçãoFormDataasync para <form action={fn}>. O React lida com o envio, fornece isPending e se integra com useActionState para gerenciar valores de retornouseActionState(actionFn, initialState) retorna [state, wrappedAction, isPending] - ele chama actionFn(prevState, formData) no envio e atualiza o estado com o resultado| Abordagem | Fonte da Verdade | Melhor Para |
|---|---|---|
Controlado (value + onChange) | Estado do React | Validação em tempo real, campos condicionais, entradas interdependentes complexas |
Não Controlado (defaultValue + ref) | DOM | Formulários simples, integrações de terceiros, entradas sensíveis ao desempenho |
| Ações de Formulário (React 19) | FormData | Mutações do lado do servidor, melhoria progressiva, redução de estado do cliente |
useActionState:
| Parâmetro | Tipo | Descrição |
|---|---|---|
action | (prevState: T, formData: FormData) => T or Promise<T> | Função chamada no envio do formulário |
initialState | T | Estado inicial antes do primeiro envio |
permalink? | string | URL opcional para melhoria progressiva com SSR |
| Retorno | Tipo | Descrição |
|---|---|---|
state | T | Estado atual (atualizado após a conclusão da ação) |
formAction | (formData: FormData) => void | Ação encapsulada para passar para <form action> |
isPending | boolean | true enquanto a ação estiver em execução |
Não Controlado com FormData (sem necessidade de useState):
function SearchForm() {
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const query = formData.get("query") as string;
router.push(`/search?q=${encodeURIComponent(query)}`);
}
return (
<form onSubmit={handleSubmit}>
<input name="query" defaultValue="" />
<button type="submit">Pesquisar</button>
</form>
);
}useFormStatus para botões de envio aninhados:
import { useFormStatus } from "react-dom";
function SubmitButton() {
const { pending } = useFormStatus();
return (
<button type="submit" disabled={pending}>
{pending ? "Salvando..." : "Salvar"}
</button>
);
}
// Uso - SubmitButton deve ser um filho de um <form>
<form action={saveAction}>
<input name="title" />
<SubmitButton />
</form>Atualizações otimistas com useOptimistic:
import { useOptimistic } from "react";
function MessageList({ messages, sendAction }: Props) {
const [optimisticMessages, addOptimistic] = useOptimistic(
messages,
(state, newMessage: string) => [
...state,
{ id: "temp", text: newMessage, sending: true },
]
);
async function handleSubmit(formData: FormData) {
const text = formData.get("message") as string;
addOptimistic(text);
await sendAction(formData);
}
return (
<form action={handleSubmit}>
<ul>
{optimisticMessages.map(msg => (
<li key={msg.id} style={{ opacity: msg.sending ? 0.5 : 1 }}>
{msg.text}
</li>
))}
</ul>
<input name="message" />
<button type="submit">Enviar</button>
</form>
);
}// Tipando onChange para diferentes tipos de input
function handleChange(e: React.ChangeEvent<HTMLInputElement>) { ... }
function handleSelectChange(e: React.ChangeEvent<HTMLSelectElement>) { ... }
function handleTextareaChange(e: React.ChangeEvent<HTMLTextAreaElement>) { ... }
// Tipando o estado da ação do formulário
interface ActionState {
success: boolean;
errors: Record<string, string>;
}
async function myAction(
prev: ActionState,
formData: FormData
): Promise<ActionState> {
// validar e retornar novo estado
}Falta de value ou onChange - Definir value sem onChange torna o input somente leitura. O React avisa sobre isso. Correção: Adicione um manipulador onChange ou use defaultValue para inputs não controlados.
defaultValue não atualiza - Alterar defaultValue após a montagem não tem efeito, pois ele apenas define o valor inicial do DOM. Correção: Use value controlado se precisar que o React impulsione as atualizações, ou adicione uma key para remontar o input.
checked de Checkbox e Rádio - Estes usam checked / defaultChecked, não value / defaultValue. Correção: <input type="checkbox" checked={isOn} onChange={e => setIsOn(e.target.checked)} />.
Number inputs retornam strings - e.target.value é sempre uma string, mesmo para <input type="number">. Correção: Analise explicitamente: Number(e.target.value) ou parseInt(e.target.value, 10).
useFormStatus fora de um formulário - useFormStatus só funciona quando o componente é renderizado como um descendente de um <form>. Chamá-lo no mesmo componente que renderiza o <form> retorna dados desatualizados. Correção: Extraia o botão de envio para um componente filho.
| Alternativa | Usar Quando | Não Usar Quando |
|---|---|---|
| React Hook Form | Validação complexa, muitos campos, formulários críticos de desempenho | Formulários simples com 1-3 campos |
| Zod + Server Actions | Mutações de servidor validadas por esquema em Next.js | Formulários apenas do lado do cliente sem servidor |
| Formik | Projetos legados que já o utilizam | Novos projetos (React Hook Form é mais leve) |
<dialog> nativo + <form method="dialog"> | Diálogos de confirmação modais | Coleta de dados de vários campos |
value + onChange - o React é a fonte da verdadedefaultValue - leia sob demanda com uma ref ou FormDataVocê pode passar uma função async diretamente para <form action={fn}>. O React a chama com um objeto FormData no envio, lida com o estado pendente e se integra com useActionState para gerenciar valores de retorno - sem necessidade de e.preventDefault().
useActionState(actionFn, initialState) retorna [state, formAction, isPending]. No envio, ele chama actionFn(prevState, formData) e atualiza state com o resultado. isPending é true enquanto a ação é executada.
Definir value sem um manipulador onChange torna o input não editável - o React trava o valor no estado. Adicione onChange para atualizar o estado, ou mude para defaultValue para um input não controlado.
defaultValue apenas define o valor inicial do DOM na montagem. Mudanças após a montagem não têm efeito. Use value controlado se o React precisar impulsionar as atualizações, ou adicione uma prop key para forçar a remontagem.
Use checked / defaultChecked em vez de value / defaultValue:
<input
type="checkbox"
checked={isEnabled}
onChange={e => setIsEnabled(e.target.checked)}
/>e.target.value é sempre uma string, mesmo para <input type="number">. Analise explicitamente: Number(e.target.value) ou parseInt(e.target.value, 10).
useFormStatus() retorna { pending } para mostrar o estado de carregamento durante uma ação de formulário. Ele deve ser chamado em um componente que seja um filho de um <form> - não no mesmo componente que renderiza o formulário.
Use um único objeto de estado e uma função de atualização genérica:
const [form, setForm] = useState({ name: "", email: "" });
function updateField(field: string, value: string) {
setForm(prev => ({ ...prev, [field]: value }));
}Use useOptimistic para mostrar imediatamente o resultado esperado enquanto a ação processa. Se a ação falhar, o React reverte automaticamente para o estado real.
<label htmlFor="id"> correspondente ao id do inputrequired, aria-describedby para mensagens de erro e aria-invalid para campos inválidosRevisado por Chris St. John·Última atualização: 16 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥