useActionState: Gerenciando Formulários e Actions no React 19

Como o hook useActionState do React 19 substitui o trio de useState para valor, erro e loading em torno de uma submissão, conectando uma Action assíncrona direto ao estado do componente.

Lidar com formulários em React sempre exigiu o mesmo bloco de código repetido: estado para o valor, estado para o erro, estado para o loading, e um onSubmit para orquestrar tudo isso com try/catch. O hook useActionState, estabilizado no React 19, assume esse trabalho: ele conecta uma função assíncrona (uma Action) ao estado de um componente e devolve o resultado mais recente, uma versão "empacotada" da função e um indicador de pendência, tudo pronto para usar.

O jeito antigo

Antes do React 19, um formulário simples já precisava de três useState e um manipulador cuidando de cada etapa manualmente:

function FormularioEmail() {
    const [email, setEmail] = useState('');
    const [erro, setErro] = useState(null);
    const [enviando, setEnviando] = useState(false);

    async function handleSubmit(e) {
        e.preventDefault();
        setEnviando(true);
        setErro(null);
        try {
            await inscrever(email);
        } catch (e) {
            setErro(e.message);
        } finally {
            setEnviando(false);
        }
    }

    return (
        <form onSubmit={handleSubmit}>
            <input value={email} onChange={e => setEmail(e.target.value)} />
            <button disabled={enviando}>Inscrever</button>
            {erro && <p>{erro}</p>}
        </form>
    );
}

Como funciona o useActionState

O hook recebe uma função action — que agora passa a receber o estado anterior como primeiro argumento — e um estado inicial, e devolve três valores: o estado atual, a Action já conectada para disparar e um booleano de pendência:

const [estado, formAction, isPending] = useActionState(
    async (estadoAnterior, formData) => {
        const email = formData.get('email');
        try {
            await inscrever(email);
            return { erro: null };
        } catch (e) {
            return { erro: e.message };
        }
    },
    { erro: null }
);

O formAction retornado vai direto no atributo action da tag <form>. O React se encarrega de chamar a função, passar um objeto FormData com os campos do formulário e atualizar o estado com o que ela retornar — sem onSubmit, sem preventDefault() e sem estado manual de erro:

function FormularioEmail() {
    const [estado, formAction, isPending] = useActionState(
        async (_, formData) => {
            try {
                await inscrever(formData.get('email'));
                return { erro: null };
            } catch (e) {
                return { erro: e.message };
            }
        },
        { erro: null }
    );

    return (
        <form action={formAction}>
            <input name="email" type="email" required />
            <button disabled={isPending}>Inscrever</button>
            {estado.erro && <p>{estado.erro}</p>}
        </form>
    );
}

Exemplo prático: contador com histórico

useActionState não é exclusivo de formulários — qualquer Action, mesmo chamada fora de um <form>, pode usar o estado anterior para acumular resultado. Aqui, cada clique soma ao total anterior:

function Contador() {
    const [total, incrementar, isPending] = useActionState(
        async (totalAnterior, incremento) => {
            await new Promise(r => setTimeout(r, 300));
            return totalAnterior + incremento;
        },
        0
    );

    return (
        <button onClick={() => incrementar(1)} disabled={isPending}>
            Total: {total}
        </button>
    );
}

Note que, aqui, a Action é disparada em um onClick comum — incrementar(1) chama a versão conectada pelo hook, passando 1 como segundo argumento da função original.

Progressive enhancement com permalink

Quando o formulário é renderizado no servidor (Server-Side Rendering) e o JavaScript ainda não carregou no cliente, o terceiro argumento opcional permalink informa para qual URL o navegador deve enviar o formulário de forma nativa, preservando o comportamento mesmo antes da hidratação:

const [estado, formAction] = useActionState(atualizarPerfil, null, '/perfil');

Quando usar

Cuidados

A função passada para useActionState precisa sempre retornar o novo estado — inclusive em caso de erro tratado dentro da própria função, já que lançar uma exceção não tratada propaga para o error boundary mais próximo em vez de atualizar o estado. Além disso, o primeiro argumento da Action é sempre o estado anterior, o que muda a assinatura de uma função existente caso você esteja migrando um onSubmit já pronto para esse padrão.

Compatibilidade

useActionState foi introduzido como useFormState nos canais experimentais do React e renomeado e estabilizado no React 19 (dezembro de 2024), disponível tanto em react quanto reexportado por react-dom. Funciona com React DOM e também com React Server Components, sem flag ou configuração extra a partir dessa versão.

documentação oficial

Conclusão

O useActionState elimina o trio clássico de useState para valor, erro e loading em torno de uma submissão, substituindo-o por um único hook que entende o ciclo de vida completo de uma Action. O resultado é menos código repetido e formulários que funcionam de forma consistente, com ou sem JavaScript totalmente carregado no cliente.

Tecnologia:
react

Deixe seu Comentário!

Dúvida, sugestão ou só um "valeu!" — tudo é bem-vindo por aqui.

Comentário
Nome
Email
lock Seu e-mail não será publicado.