Busque em todas as páginas da documentação
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
"use client";
import { useActionState } from "react";
import { useFormStatus } from "react-dom";
async function subscribe(_prev: string, formData: FormData): Promise<string> {
const email = formData.get("email") as string;
const res = await fetch("/api/subscribe", {
method: "POST",
body: JSON.stringify({ email }),
});
if (!res.ok) return "Algo deu errado.";
return "Inscrito!";
}
function SubmitButton() {
const { pending } = useFormStatus();
return (
<button type="submit" disabled={pending}>
{pending ? "Inscrevendo..." : "Inscrever-se"}
</button>
);
}
export default function NewsletterForm() {
const [message, formAction, isPending] = useActionState(subscribe, "");
return (
<form action={formAction}>
<input name="email" type="email" required placeholder="voce@exemplo.com" />
<SubmitButton />
{message && <p>{message}</p>}
</form>
);
}Quando usar isso: Sempre que você tiver um formulário que envia dados -- cadastros, buscas, operações CRUD. As ações de formulário substituem o padrão manual onSubmit + preventDefault + useState.
// Um formulário de contato com validação, exibição de erros e aprimoramento progressivo
"use client";
import { useActionState } from "react";
import { useFormStatus } from "react-dom";
type FormState = {
success: boolean;
errors: Record<string, string>;
message: string;
};
const initialState: FormState = {
success: false,
errors: {},
message: "",
};
async function submitContact(_prev: FormState, formData: FormData): Promise<FormState> {
const name = formData.get("name") as string;
const email = formData.get("email") as string;
const body = formData.get("body") as string;
// Validação no lado do cliente
const errors: Record<string, string> = {};
if (!name || name.length < 2) errors.name = "O nome deve ter pelo menos 2 caracteres.";
if (!email || !email.includes("@")) errors.email = "Por favor, insira um e-mail válido.";
if (!body || body.length < 10) errors.body = "A mensagem deve ter pelo menos 10 caracteres.";
if (Object.keys(errors).length > 0) {
return { success: false, errors, message: "Por favor, corrija os erros abaixo." };
}
try {
const res = await fetch("/api/contact", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name, email, body }),
});
if (!res.ok) throw new Error("Erro do servidor");
return { success: true, errors: {}, message: "Mensagem enviada! Entraremos em contato." };
} catch {
return { success: false, errors: {}, message: "Falha ao enviar. Por favor, tente novamente." };
}
}
function FieldError({ error }: { error?: string }) {
if (!error) return null;
return <p className="text-red-500 text-sm mt-1">{error}</p>;
}
function SubmitButton() {
const { pending } = useFormStatus();
return (
<button
type="submit"
disabled={pending}
className="bg-blue-600 text-white px-4 py-2 rounded disabled:opacity-50"
>
{pending ? "Enviando..." : "Enviar Mensagem"}
</button>
);
}
export default function ContactForm() {
const [state, formAction, isPending] = useActionState(submitContact, initialState);
return (
<form action={formAction} className="space-y-4 max-w-md">
<div>
<label htmlFor="name">Nome</label>
<input id="name" name="name" className="border w-full p-2 rounded" />
<FieldError error={state.errors.name} />
</div>
<div>
<label htmlFor="email">E-mail</label>
<input id="email" name="email" type="email" className="border w-full p-2 rounded" />
<FieldError error={state.errors.email} />
</div>
<div>
<label htmlFor="body">Mensagem</label>
<textarea id="body" name="body" rows={4} className="border w-full p-2 rounded" />
<FieldError error={state.errors.body} />
</div>
<SubmitButton />
{state.message && (
<p className={state.success ? "text-green-600" : "text-red-600"}>
{state.message}
</p>
)}
</form>
);
}O que isso demonstra:
useActionState gerenciando o estado do formulário entre envios (erros, mensagem de sucesso)useFormStatus em um componente filho para mostrar o estado de pendência no botão de envio<form action={fn}> -- O React 19 estende o atributo nativo action para aceitar funções assíncronas. Quando o formulário é enviado, o React chama fn(formData) e gerencia o ciclo de vida de pendência.useActionState(action, initialState, permalink?) -- Encapsula uma função de ação e retorna [state, wrappedAction, isPending]. Cada vez que o formulário é enviado, action(prevState, formData) é chamado e o valor retornado se torna o novo state. O parâmetro opcional permalink permite o aprimoramento progressivo para ações de servidor.useFormStatus() -- Deve ser chamado de um componente renderizado dentro de um <form>. Retorna { pending, data, method, action } refletindo o estado de envio do formulário pai mais próximo.useActionState, os formulários podem funcionar antes que o JavaScript seja carregado. O argumento permalink especifica para onde redirecionar após a conclusão da ação do servidor no caso sem JS.isPending de useActionState reflete se a ação está atualmente em execução. Isso está disponível no mesmo componente que chama useActionState, ao contrário de useFormStatus, que só funciona em descendentes.Múltiplos botões de envio com ações diferentes:
"use client";
import { useActionState } from "react";
export default function ItemForm() {
const [saveResult, saveAction] = useActionState(saveItem, null);
const [deleteResult, deleteAction] = useActionState(deleteItem, null);
return (
<form>
<input name="name" />
<button formAction={saveAction}>Salvar</button>
<button formAction={deleteAction}>Excluir</button>
</form>
);
}Usando useFormStatus para um indicador de carregamento global:
"use client";
import { useFormStatus } from "react-dom";
export function FormProgress() {
const { pending, data } = useFormStatus();
if (!pending) return null;
return (
<div className="fixed top-0 left-0 w-full h-1 bg-blue-500 animate-pulse" />
);
}
// Use dentro de qualquer formulário
<form action={myAction}>
<FormProgress />
{/* ... campos ... */}
</form>Redefinir formulário após o sucesso:
"use client";
import { useActionState, useRef, useEffect } from "react";
export default function ResetableForm() {
const [state, formAction, isPending] = useActionState(submitData, { success: false });
const formRef = useRef<HTMLFormElement>(null);
useEffect(() => {
if (state.success) {
formRef.current?.reset();
}
}, [state]);
return (
<form ref={formRef} action={formAction}>
<input name="item" required />
<button type="submit" disabled={isPending}>Adicionar</button>
</form>
);
}useActionState é genérico: useActionState<State>(fn: (prev: State, formData: FormData) => Promise<State>, initial: State).useFormStatus retorna { pending: boolean; data: FormData | null; method: string; action: string | ((formData: FormData) => void) | null }.(previousState: State, formData: FormData) => State | Promise<State>.permalink em useActionState é tipado como string | undefined.useFormStatus deve estar dentro de um descendente de <form> -- Chamá-lo no mesmo componente que renderiza a tag <form> sempre retorna { pending: false }. Correção: Extraia o botão de envio para um componente filho.return { ...prev, error: "..." }.useRef no formulário e chame formRef.current?.reset() em um efeito.useActionState -- É importado de "react", não de "react-dom". O antigo useFormState estava em "react-dom" e agora está obsoleto. Correção: import { useActionState } from "react".formData.useActionState é independente. O envio de um formulário não afeta outro. Correção: Este é o comportamento esperado, mas esteja ciente de que useFormStatus reflete apenas o formulário ancestral mais próximo.| Abordagem | Quando escolher |
|---|---|
Ações de Formulário + useActionState | Formulários padrão do React 19 com estado de pendência integrado |
react-hook-form | Validação complexa no lado do cliente, arrays de campos, comportamento de observação |
| Formik | Projetos legados que já usam Formik |
onSubmit manual + fetch | Controle total sobre o ciclo de vida da requisição, cabeçalhos personalizados |
| Apenas ação de servidor (sem estado do cliente) | Mutações simples que redirecionam após a conclusão |
| Conform | Aprimoramento progressivo com integração Zod para ações de servidor |
<form action={fn}> permite que o React gerencie o estado de pendência, tratamento de erros e atualizações otimistas automaticamenteonSubmit + preventDefault + useState requer gerenciamento manual de estado[state, wrappedAction, isPending]state é atualizado a cada vez que a ação é concluída, com base no que a ação retornawrappedAction é passado para <form action={}> e isPending indica se a ação está em execução no momentouseFormStatus é importado de "react-dom", enquanto useActionState é de "react"useFormStatus deve ser chamado de um componente renderizado dentro de um <form> (um descendente)isPending de useActionState está disponível no mesmo componente que renderiza o formulárioRetorne um objeto de erros da ação e renderize-o por campo:
async function submit(_prev: State, formData: FormData) {
const errors: Record<string, string> = {};
if (!formData.get("name")) errors.name = "Obrigatório";
if (Object.keys(errors).length) return { errors };
// ... salvar dados
return { errors: {} };
}const formRef = useRef<HTMLFormElement>(null);
const [state, formAction] = useActionState(submit, initial);
useEffect(() => {
if (state.success) formRef.current?.reset();
}, [state]);
<form ref={formRef} action={formAction}>...</form>As ações de formulário do React não redefinem o formulário automaticamente, ao contrário dos envios de formulário nativos.
Sim. Use o atributo formAction em botões individuais:
<form>
<input name="name" />
<button formAction={saveAction}>Salvar</button>
<button formAction={deleteAction}>Excluir</button>
</form>useFormStatus deve ser chamado de um descendente do elemento <form>, não do mesmo componente que o renderizauseFormStatusreturn { ...prev, error: "..." }useActionState<State>(
fn: (prev: State, formData: FormData) => Promise<State>,
initial: State
): [State, (formData: FormData) => void, boolean]A assinatura da função de ação é (previousState: State, formData: FormData) => State | Promise<State>.
{ pending: boolean; data: FormData | null; method: string; action: string | ((formData: FormData) => void) | null }data é o FormData que está sendo enviado (ou null quando não está pendente)method reflete o método HTTP do formulárioFormDataRevisado por Chris St. John·Última atualização: 16 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥