O que é o hook useActionState e como ele funciona?
O React 19 veio com dois novos recursos notáveis chamados server components e server actions.
A partir dessa versão, os componentes de servidor se tornaram o padrão em frameworks como Next.js que os suportam prontamente.
As ações do servidor, por outro lado, são funções que são executadas no servidor para permitir o manuseio de formulários diretamente no servidor sem a necessidade de endpoints de API.
Uma ação de servidor se parece com isto:
"use server";
async function submitForm(formData) {
const name = formData.get("name");
return { message: Hello, ${name}! };
}
Esta ação do servidor extrai um campo name de um formulário e retorna uma string saudando esse nome.
Para simplificar o gerenciamento de estado para ações do servidor e eliminar a necessidade de JavaScript no lado do cliente para formulários simples, a equipe do React introduziu o hook useActionState na versão 19.
Vamos dar uma olhada mais de perto neste hook e ver como ele funciona.
A documentação do React descreve o hook useActionState como um hook que "permite atualizar o estado com base no resultado de uma ação de formulário."
Mas isso não significa que você pode usar o hook useActionState apenas com formulários. Você também pode usá-lo para gerenciar cliques em botões e outros eventos, desde que você tenha uma ação configurada.
E lembre-se de que, como useActionState é um hook, você não pode usá-lo dentro de um componente de servidor.
Aqui está a sintaxe básica do hook useActionState:
const [state, action, isPending] = useActionState(actionFunction, initialState, permalink);
stateé o estado atual que a ação retorna.actioné a função que dispara a ação do servidor.isPendingé um booleano que indica se a ação está sendo executada no momento ou não.- O parâmetro
actionFunctioné a própria ação do servidor. initialStateé o parâmetro que representa o ponto de partida para o estado antes da execução da ação.permalinké uma string opcional que contém a URL única da página que o formulário modifica.
useActionState, certifique-se de que você tenha uma action configurada primeiro. Vamos usar a ação do exemplo anterior para isso, com uma pequena variação:
"use server";
export async function submitForm(_, formData) {
const name = formData.get("name");
const hour = new Date().getHours();
let greeting;
if (hour < 12) {
greeting = "Good morning";
} else if (hour < 18) {
greeting = "Good afternoon";
} else {
greeting = "Good evening";
}
return { message: ${greeting}, ${name} };
}
No seu componente, você precisa importar o hook useActionState e chamá-lo no nível superior do corpo do componente (antes da declaração return) assim como outros hooks. Você também deve importar a action:
"use client";
// Import the useActionState hook
import { useActionState } from "react";
// Import the submitForm action
import { submitForm } from "./actions/submitForm";
const Greeter = () => {
// Initialize the hook
const [state, submit, isPending] = useActionState(submitForm, {
message: "",
});
return (
<div className="flex flex-col items-center justify-center min-h-screen bg-gray-100 p-6">
{/* Rest of component */}
</div>
);
};
export default Greeter;
Aqui está como o código completo fica com um pouco de estilo:
"use client";
import { useActionState } from "react";
import { submitForm } from "./actions/submitForm";
const Greeter = () => {
const [state, submit, isPending] = useActionState(submitForm, {
message: "",
});
return (
<div className="flex flex-col items-center justify-center min-h-screen bg-gray-100 p-6">
<form
action={submit}
className="bg-white p-6 rounded-2xl shadow-md w-full max-w-md"
>
<h2 className="text-2xl text-center font-semibold text-gray-700 mb-4">
Greet Someone
</h2>
<input
type="text"
name="name"
placeholder="Enter your name"
required
className="w-full p-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-green-400"
/>
<button
type="submit"
disabled={isPending}
className="w-full mt-4 p-3 bg-green-500 text-white font-semibold rounded-lg hover:bg-green-600 disabled:bg-gray-400 transition-all"
>
{isPending ? "Greeting..." : "Greet"}
</button>
{state.message && (
<p className="mt-4 text-green-600 text-center font-medium">
{state.message}
</p>
)}
</form>
</div>
);
};
export default Greeter;
No navegador, você veria o botão do seu formulário mudar de Greet para Greeting... enquanto a ação isPending está em andamento - e a saudação mostraria Good morning, {name}, Good afternoon, {name} ou Good evening, {name}, dependendo da hora do dia em que o formulário foi enviado.
Lembra como mencionamos que você também pode usar o hook useActionState fora de um formulário?
Neste exemplo, vamos buscar cinco usuários do JSONPlaceholder com um clique no botão:
"use server";
export async function getUsers() {
const res = await fetch(
"https://jsonplaceholder.typicode.com/users?_start=0&_limit=5/"
);
return await res.json();
}
Aqui está a interface estilizada:
"use client";
import { useActionState } from "react";
import { getUsers } from "./actions/getUsers";
export default function FetchUsers() {
const [users, fetchAction, isPending] = useActionState(getUsers, []);
return (
<div className="p-6 max-w-lg mx-auto">
<button
onClick={fetchAction}
disabled={isPending}
className="px-4 py-2 cursor-pointer bg-green-500 text-white rounded-lg hover:bg-green-600 disabled:bg-gray-400 font-bold"
>
{isPending ? "Fetching Users..." : "Fetch Users"}
</button>
<ul className="mt-4 space-y-2">
{users.map((user) => (
<li key={user.id} className="p-3 bg-gray-100 rounded-lg">
<p className="font-semibold">{user.name}</p>
<p className="text-sm text-gray-600">{user.email}</p>
</li>
))}
</ul>
</div>
);
}
No navegador, você veria que o texto do botão nunca é atualizado para Fetching Users... depois que ele é clicado.
Isso acontece porque o React trata a busca de dados e a renderização como uma prioridade maior do que o estado isPending, que bloqueia o isPending no processo e gera um erro.
Para corrigir esse problema, você precisa envolver a ação em startTransition:
"use client";
// import startTransition from React
import { useActionState, startTransition } from "react";
import { getUsers } from "./actions/getUsers";
export default function FetchUsers() {
const [users, fetchAction, isPending] = useActionState(getUsers, []);
return (
<div className="p-6 max-w-lg mx-auto">
<button
{/* wrap fetchAction in startTransition */}
onClick={() => startTransition(() => fetchAction())}
disabled={isPending}
className="px-4 py-2 bg-green-500 font-bold cursor-pointer text-white rounded-lg hover:bg-green-600 disabled:bg-gray-400"
>
{isPending ? 'Fetching Users...' : 'Fetch Users'}
</button>
<ul className="mt-4 space-y-2">
{users.map((user) => (
<li key={user.id} className="p-3 bg-gray-100 rounded-lg">
<p className="font-semibold">{user.name}</p>
<p className="text-sm text-gray-600">{user.email}</p>
</li>
))}
</ul>
</div>
);
}
Se você está se perguntando o que é startTransition, é uma função que informa ao React que uma atualização de estado é de baixa prioridade e pode ser interrompida. Isso mantém a interface do usuário responsiva enquanto lida com atualizações assíncronas como server actions.
É assim que se usa o hook useActionState dentro e fora de um formulário.Este módulo não possui perguntas. Marque como concluído.