Português

Desbloqueie o poder do hook useActionState do React. Aprenda como ele simplifica o gerenciamento de formulários, lida com estados pendentes e melhora a experiência do usuário com exemplos práticos e detalhados.

React useActionState: Um Guia Abrangente para o Gerenciamento Moderno de Formulários

O mundo do desenvolvimento web está em constante evolução, e o ecossistema React está na vanguarda dessa mudança. Com as versões recentes, o React introduziu recursos poderosos que melhoram fundamentalmente a forma como construímos aplicações interativas e resilientes. Entre os mais impactantes está o hook useActionState, um divisor de águas para o manuseio de formulários e operações assíncronas. Este hook, anteriormente conhecido como useFormState em versões experimentais, é agora uma ferramenta estável e essencial para qualquer desenvolvedor React moderno.

Este guia abrangente levará você a um mergulho profundo no useActionState. Exploraremos os problemas que ele resolve, sua mecânica central e como aproveitá-lo ao lado de hooks complementares como useFormStatus para criar experiências de usuário superiores. Esteja você construindo um simples formulário de contato ou uma aplicação complexa e intensiva em dados, entender o useActionState tornará seu código mais limpo, mais declarativo e mais robusto.

O Problema: A Complexidade do Gerenciamento de Estado de Formulários Tradicional

Antes que possamos apreciar a elegância do useActionState, devemos primeiro entender os desafios que ele aborda. Durante anos, gerenciar o estado de formulários no React envolveu um padrão previsível, mas muitas vezes complicado, usando o hook useState.

Vamos considerar um cenário comum: um formulário simples para adicionar um novo produto a uma lista. Precisamos gerenciar várias partes do estado:

Uma implementação típica poderia se parecer com algo assim:

Exemplo: A 'Maneira Antiga' com múltiplos hooks useState

// Função de API fictícia
const addProductAPI = async (productName) => {
await new Promise(resolve => setTimeout(resolve, 1500));
if (!productName || productName.length < 3) {
throw new Error('O nome do produto deve ter pelo menos 3 caracteres.');
}
console.log(`Produto "${productName}" adicionado.`);
return { success: true };
};

// O componente
import { useState } from 'react';

function OldProductForm() {
const [productName, setProductName] = useState('');
const [error, setError] = useState(null);
const [isPending, setIsPending] = useState(false);

const handleSubmit = async (event) => {
event.preventDefault();
setIsPending(true);
setError(null);

try {
await addProductAPI(productName);
setProductName(''); // Limpa o input em caso de sucesso
} catch (err) {
setError(err.message);
} finally {
setIsPending(false);
}
};

return (




id="productName"
name="productName"
value={productName}
onChange={(e) => setProductName(e.target.value)}
/>

{error &&

{error}

}


);
}

Essa abordagem funciona, mas tem várias desvantagens:

  • Código Repetitivo (Boilerplate): Precisamos de três chamadas separadas de useState para gerenciar o que é conceitualmente um único processo de envio de formulário.
  • Gerenciamento Manual de Estado: O desenvolvedor é responsável por definir e redefinir manualmente os estados de carregamento e erro na ordem correta dentro de um bloco try...catch...finally. Isso é repetitivo e propenso a erros.
  • Acoplamento: A lógica para lidar com o resultado do envio do formulário está fortemente acoplada à lógica de renderização do componente.

Apresentando o useActionState: Uma Mudança de Paradigma

useActionState é um hook do React projetado especificamente para gerenciar o estado de uma ação assíncrona, como o envio de um formulário. Ele simplifica todo o processo conectando o estado diretamente ao resultado da função de ação.

Sua assinatura é clara e concisa:

const [state, formAction] = useActionState(actionFn, initialState);

Vamos analisar seus componentes:

  • actionFn(previousState, formData): Esta é a sua função assíncrona que realiza o trabalho (por exemplo, chama uma API). Ela recebe o estado anterior e os dados do formulário como argumentos. Crucialmente, o que quer que esta função retorne se torna o novo estado.
  • initialState: Este é o valor do estado antes da ação ser executada pela primeira vez.
  • state: Este é o estado atual. Ele mantém o initialState inicialmente e é atualizado para o valor de retorno da sua actionFn após cada execução.
  • formAction: Esta é uma nova versão encapsulada (wrapped) da sua função de ação. Você deve passar esta função para a prop action do elemento <form>. O React usa essa função encapsulada para rastrear o estado pendente da ação.

Exemplo Prático: Refatorando com useActionState

Agora, vamos refatorar nosso formulário de produto usando o useActionState. A melhoria é imediatamente aparente.

Primeiro, precisamos adaptar nossa lógica de ação. Em vez de lançar erros, a ação deve retornar um objeto de estado que descreve o resultado.

Exemplo: A 'Nova Maneira' com useActionState

// A função de ação, projetada para funcionar com o useActionState
const addProductAction = async (previousState, formData) => {
const productName = formData.get('productName');
await new Promise(resolve => setTimeout(resolve, 1500)); // Simula um atraso na rede

if (!productName || productName.length < 3) {
return { message: 'O nome do produto deve ter pelo menos 3 caracteres.', success: false };
}

console.log(`Produto "${productName}" adicionado.`);
// Em caso de sucesso, retorna uma mensagem de sucesso e limpa o formulário.
return { message: `"${productName}" adicionado com sucesso`, success: true };
};

// O componente refatorado
import { useActionState } from 'react';
// Nota: Adicionaremos o useFormStatus na próxima seção para lidar com o estado pendente.

function NewProductForm() {
const initialState = { message: null, success: false };
const [state, formAction] = useActionState(addProductAction, initialState);

return (





{!state.success && state.message && (

{state.message}


)}
{state.success && state.message && (

{state.message}


)}

);
}

Veja como isso é muito mais limpo! Substituímos três hooks useState por um único hook useActionState. A responsabilidade do componente agora é puramente renderizar a UI com base no objeto `state`. Toda a lógica de negócios está elegantemente encapsulada na função `addProductAction`. O estado é atualizado automaticamente com base no que a ação retorna.

Mas espere, e o estado pendente? Como desabilitamos o botão enquanto o formulário está sendo enviado?

Lidando com Estados Pendentes com useFormStatus

O React fornece um hook complementar, useFormStatus, projetado para resolver exatamente este problema. Ele fornece informações de status para o último envio de formulário, mas com uma regra crucial: ele deve ser chamado de um componente que é renderizado dentro do <form> cujo status você deseja rastrear.

Isso incentiva uma separação limpa de responsabilidades. Você cria um componente especificamente para elementos da UI que precisam estar cientes do status de envio do formulário, como um botão de envio.

O hook useFormStatus retorna um objeto com várias propriedades, das quais a mais importante é `pending`.

const { pending, data, method, action } = useFormStatus();

  • pending: Um booleano que é `true` se o formulário pai estiver sendo enviado e `false` caso contrário.
  • data: Um objeto `FormData` contendo os dados que estão sendo enviados.
  • method: Uma string indicando o método HTTP (`'get'` ou `'post'`).
  • action: Uma referência à função passada para a prop `action` do formulário.

Criando um Botão de Envio Consciente do Status

Vamos criar um componente `SubmitButton` dedicado e integrá-lo ao nosso formulário.

Exemplo: O componente SubmitButton

import { useFormStatus } from 'react-dom';
// Nota: useFormStatus é importado de 'react-dom', não de 'react'.

function SubmitButton() {
const { pending } = useFormStatus();

return (

);
}

Agora, podemos atualizar nosso componente de formulário principal para usá-lo.

Exemplo: O formulário completo com useActionState e useFormStatus

import { useActionState } from 'react';
import { useFormStatus } from 'react-dom';

// ... (a função addProductAction permanece a mesma)

function SubmitButton() { /* ... como definido acima ... */ }

function CompleteProductForm() {
const initialState = { message: null, success: false };
const [state, formAction] = useActionState(addProductAction, initialState);

return (



{/* Podemos adicionar uma chave (key) para resetar o input em caso de sucesso */}


{!state.success && state.message && (

{state.message}


)}
{state.success && state.message && (

{state.message}


)}

);
}

Com essa estrutura, o componente `CompleteProductForm` não precisa saber nada sobre o estado pendente. O `SubmitButton` é totalmente autônomo. Esse padrão de composição é incrivelmente poderoso para construir UIs complexas e de fácil manutenção.

O Poder do Aprimoramento Progressivo (Progressive Enhancement)

Um dos benefícios mais profundos dessa nova abordagem baseada em ações, especialmente quando usada com Server Actions, é o aprimoramento progressivo automático. Este é um conceito vital para construir aplicações para um público global, onde as condições de rede podem ser instáveis e os usuários podem ter dispositivos mais antigos ou JavaScript desabilitado.

Veja como funciona:

  1. Sem JavaScript: Se o navegador de um usuário não executar o JavaScript do lado do cliente, o `<form action={...}>` funciona como um formulário HTML padrão. Ele faz uma requisição de página inteira para o servidor. Se você estiver usando um framework como o Next.js, a ação do lado do servidor é executada, e o framework renderiza novamente a página inteira com o novo estado (por exemplo, mostrando o erro de validação). A aplicação é totalmente funcional, apenas sem a fluidez de uma SPA.
  2. Com JavaScript: Assim que o pacote JavaScript carrega e o React hidrata a página, a mesma `formAction` é executada do lado do cliente. Em vez de um recarregamento de página inteira, ele se comporta como uma requisição fetch típica. A ação é chamada, o estado é atualizado e apenas as partes necessárias do componente são renderizadas novamente.

Isso significa que você escreve sua lógica de formulário uma vez, e ela funciona perfeitamente em ambos os cenários. Você constrói uma aplicação resiliente e acessível por padrão, o que é uma grande vitória para a experiência do usuário em todo o mundo.

Padrões Avançados e Casos de Uso

1. Server Actions vs. Client Actions

A `actionFn` que você passa para o useActionState pode ser uma função assíncrona padrão do lado do cliente (como em nossos exemplos) ou uma Server Action. Uma Server Action é uma função definida no servidor que pode ser chamada diretamente de componentes do cliente. Em frameworks como o Next.js, você define uma adicionando a diretiva "use server"; no topo do corpo da função.

  • Client Actions: Ideais para mutações que afetam apenas o estado do lado do cliente ou chamam APIs de terceiros diretamente do cliente.
  • Server Actions: Perfeitas para mutações que envolvem um banco de dados ou outros recursos do lado do servidor. Elas simplificam sua arquitetura, eliminando a necessidade de criar manualmente endpoints de API para cada mutação.

A beleza é que o useActionState funciona de forma idêntica com ambos. Você pode trocar uma ação do cliente por uma ação do servidor sem alterar o código do componente.

2. Atualizações Otimistas com `useOptimistic`

Para uma sensação ainda mais responsiva, você pode combinar o useActionState com o hook useOptimistic. Uma atualização otimista é quando você atualiza a UI imediatamente, *assumindo* que a ação assíncrona será bem-sucedida. Se ela falhar, você reverte a UI para o estado anterior.

Imagine um aplicativo de mídia social onde você adiciona um comentário. De forma otimista, você mostraria o novo comentário na lista instantaneamente enquanto a requisição está sendo enviada ao servidor. O useOptimistic é projetado para funcionar em conjunto com as ações para tornar esse padrão simples de implementar.

3. Resetando um Formulário em Caso de Sucesso

Um requisito comum é limpar os campos do formulário após um envio bem-sucedido. Existem algumas maneiras de conseguir isso com o useActionState.

  • O Truque da Prop `key`: Como mostrado em nosso exemplo `CompleteProductForm`, você pode atribuir uma `key` única a um input ou a todo o formulário. Quando a chave muda, o React desmontará o componente antigo e montará um novo, efetivamente resetando seu estado. Vincular a chave a uma flag de sucesso (`key={state.success ? 'success' : 'initial'}`) é um método simples e eficaz.
  • Componentes Controlados: Você ainda pode usar componentes controlados se necessário. Gerenciando o valor do input com useState, você pode chamar a função setter para limpá-lo dentro de um useEffect que escuta o estado de sucesso do useActionState.

Erros Comuns e Melhores Práticas

  • Posicionamento do useFormStatus: Lembre-se, um componente que chama useFormStatus deve ser renderizado como filho do `<form>`. Ele não funcionará se for um irmão ou pai.
  • Estado Serializável: Ao usar Server Actions, o objeto de estado retornado da sua ação deve ser serializável. Isso significa que ele não pode conter funções, Símbolos ou outros valores não serializáveis. Limite-se a objetos simples, arrays, strings, números e booleanos.
  • Não Lance Erros nas Ações: Em vez de `throw new Error()`, sua função de ação deve lidar com erros de forma elegante e retornar um objeto de estado que descreve o erro (por exemplo, `{ success: false, message: 'Ocorreu um erro' }`). Isso garante que o estado seja sempre atualizado de forma previsível.
  • Defina uma Estrutura de Estado Clara: Estabeleça uma estrutura consistente para o seu objeto de estado desde o início. Uma estrutura como `{ data: T | null, message: string | null, success: boolean, errors: Record | null }` pode cobrir muitos casos de uso.

useActionState vs. useReducer: Uma Comparação Rápida

À primeira vista, o useActionState pode parecer semelhante ao useReducer, já que ambos envolvem a atualização do estado com base em um estado anterior. No entanto, eles servem a propósitos distintos.

  • useReducer é um hook de propósito geral para gerenciar transições de estado complexas no lado do cliente. É acionado pelo despacho de ações e é ideal para lógicas de estado que têm muitas mudanças de estado síncronas possíveis (por exemplo, um assistente complexo de várias etapas).
  • useActionState é um hook especializado, projetado para o estado que muda em resposta a uma única ação, tipicamente assíncrona. Seu papel principal é integrar-se com formulários HTML, Server Actions e os recursos de renderização concorrente do React, como transições de estado pendentes.

A conclusão é: Para envios de formulário e operações assíncronas ligadas a formulários, useActionState é a ferramenta moderna e construída para esse propósito. Para outras máquinas de estado complexas do lado do cliente, useReducer continua sendo uma excelente escolha.

Conclusão: Abraçando o Futuro dos Formulários no React

O hook useActionState é mais do que apenas uma nova API; ele representa uma mudança fundamental em direção a uma maneira mais robusta, declarativa e centrada no usuário de lidar com formulários e mutações de dados no React. Ao adotá-lo, você ganha:

  • Redução de Código Repetitivo: Um único hook substitui múltiplas chamadas de useState e orquestração manual de estado.
  • Estados Pendentes Integrados: Lide de forma transparente com UIs de carregamento com o hook complementar useFormStatus.
  • Aprimoramento Progressivo Embutido: Escreva código que funciona com ou sem JavaScript, garantindo acessibilidade e resiliência para todos os usuários.
  • Comunicação Simplificada com o Servidor: Um encaixe natural para Server Actions, simplificando a experiência de desenvolvimento full-stack.

Ao iniciar novos projetos ou refatorar os existentes, considere usar o useActionState. Ele não apenas melhorará sua experiência como desenvolvedor, tornando seu código mais limpo e previsível, mas também o capacitará a construir aplicações de maior qualidade que são mais rápidas, mais resilientes e acessíveis a um público global diversificado.