El hook useActionState es una poderosa adición a React (disponible de forma estable y funcional en React 19) diseñado para simplificar la gestión del estado en respuesta a acciones de formularios o cualquier función asíncrona (Action).
isPending).La sintaxis del useActionState es concisa y devuelve hasta tres valores clave:
import { useActionState } from "react";
const [state, formAction, isPending] = useActionState(actionFn, initialState); | Argumento | Descripción |
|---|---|
actionFn | La función asíncrona (Action) que se ejecutará al enviar el formulario. Recibe dos argumentos: (previousState, formData). Debe devolver el nuevo estado. |
initialState | El valor inicial del estado antes de que se ejecute cualquier acción. Puede ser un objeto, cadena, número, etc. |
permalink (Opcional) | Una URL que el formulario modifica. Útil para preservar el estado en páginas con contenido dinámico. |
| Valor | Descripción |
|---|---|
state | El estado actual del componente. Inicialmente es initialState, y luego se actualiza con lo que devuelva actionFn. |
formAction | Una función prop que se pasa directamente a la prop action de la etiqueta <form> o a la prop formAction de un botón. |
isPending (Opcional) | Un booleano que es true mientras la acción se está ejecutando (ideal para deshabilitar botones o mostrar spinners). |
Este ejemplo ilustra cómo useActionState puede manejar una actualización de estado asíncrona (simulada con un setTimeout) de una manera más limpia.
Explicación:
El componente CounterForm usa useActionState para gestionar el conteo. Cada vez que se envía el formulario, se llama a la función increment. Esta función recibe el estado anterior (previousState) y simplemente le suma 1, devolviendo el nuevo estado que React aplica automáticamente.
// Debe ser un componente de cliente si maneja la interacción del DOM.
"use client";
import { useActionState } from "react";
// 1. La función Action (simulando una operación asíncrona en el servidor)
async function increment(previousState, formData) {
// previousState es el valor actual de 'state' (el contador).
// Simulación de un retraso asíncrono (ej. una llamada a base de datos)
await new Promise(resolve => setTimeout(resolve, 1000));
// Devuelve el nuevo estado.
return previousState + 1;
}
function CounterForm() {
// Inicializamos el estado en 0. 'state' es el valor, 'formAction' es la prop del form.
const [state, formAction] = useActionState(increment, 0);
return (
// 2. Conectamos la función 'formAction' a la prop 'action' del formulario.
<form action={formAction}>
<p>Contador: {state}</p>
{/* 3. El botón de tipo submit activa la action. */}
<button type="submit">Incrementar</button>
</form>
);
}
export default CounterForm; isPending) y Mensajes de RetroalimentaciónUsaremos isPending para manejar el estado de carga y el state como objeto para manejar mensajes de éxito/error.
Explicación:
El hook devuelve un tercer valor, isPending, que es true mientras la acción submitForm está en curso. La acción devuelve un objeto con un indicador de success y un message. El state se inicializa como un objeto vacío, y luego toma el valor de retorno de submitForm, permitiendo mostrar el mensaje de retroalimentación y deshabilitar el botón durante la carga.
"use client";
import { useActionState } from "react";
// La función de acción que procesa los datos y devuelve el nuevo estado (mensaje).
async function submitForm(prevState, formData) {
// Simulación de un proceso de envío (ej. una petición API)
await new Promise((resolve) => setTimeout(resolve, 1500));
// Accedemos a los datos del formulario usando formData.get()
const email = formData.get("email");
// Lógica de validación
if (!email || !email.includes("@")) {
// Si falla, devolvemos el nuevo estado con un mensaje de error.
return {
success: false,
message: "Por favor, introduce una dirección de correo válida."
};
}
// Si tiene éxito, devolvemos el nuevo estado con un mensaje de éxito.
return {
success: true,
message: "¡Formulario enviado con éxito!"
};
}
function FeedbackForm() {
// El estado inicial es un objeto para almacenar el éxito y el mensaje.
// Capturamos el tercer valor: isPending.
const [state, formAction, isPending] = useActionState(submitForm, {
success: null,
message: ""
});
return (
<form action={formAction}>
<input type="text" name="name" placeholder="Nombre" />
<input type="email" name="email" placeholder="Correo Electrónico" />
{/* Deshabilitamos el botón mientras la acción está pendiente */}
<button type="submit" disabled={isPending}>
{isPending ? "Enviando..." : "Enviar"}
</button>
{/* Mostramos el mensaje de retroalimentación usando el estado */}
{state.message && (
<p className={state.success ? "success" : "error"}>
{state.message}
</p>
)}
</form>
);
}
export default FeedbackForm; useActionState es ideal para la comunicación entre el Cliente y las Server Actions (funciones asíncronas de React marcadas con "use server").
Explicación:
Definimos la Server Action toggleLike. En el cliente, useActionState gestiona el envío del formulario, llama a la acción en el servidor y actualiza el estado liked con el valor de retorno (!prevLiked) automáticamente.
// --- Archivo: actions.ts (Server Component) ---
"use server";
// Simula la actualización del estado "Me Gusta" en una base de datos.
export async function toggleLike(prevLiked) {
// Simulación de espera del servidor
await new Promise((res) => setTimeout(res, 1000));
// El nuevo estado es el opuesto al anterior (true/false)
return !prevLiked;
}
// --- Archivo: LikeButton.tsx (Client Component) ---
"use client";
import { useActionState } from "react";
// Importamos la Server Action
import { toggleLike } from "./actions";
function LikeButton() {
// Estado inicial es 'false' (no le gusta).
const [liked, formAction] = useActionState(toggleLike, false);
return (
// Conectamos el formulario con la Server Action
<form action={formAction}>
<button className="like-button">
{/* Usamos el estado 'liked' para cambiar el texto del botón */}
{liked ? " ❤️ Me Gusta" : "♡ Me Gusta"}
</button>
</form>
);
}
export default LikeButton; Podemos usar el hook useActionState varias veces dentro del mismo componente para gestionar acciones completamente independientes, cada una con su propio estado aislado.
Explicación:
El componente maneja dos funcionalidades separadas (“Me Gusta” y “Seguir”). Cada funcionalidad usa su propia llamada a useActionState para gestionar su lógica asíncrona y su estado de manera aislada.
// --- Archivo: actions.ts (Server Component) ---
"use server";
// Definimos las dos acciones asíncronas
export async function toggleLike(prevLiked) {
await new Promise((res) => setTimeout(res, 800));
return !prevLiked;
}
export async function toggleFollow(prevFollowing) {
// Simulación de espera del servidor
await new Promise((res) => setTimeout(res, 1000));
// El nuevo estado es el opuesto al anterior (true/false)
return !prevFollowing;
}
// --- Archivo: SocialActions.tsx (Client Component) ---
"use client";
import { useActionState } from "react";
import { toggleLike, toggleFollow } from "./actions";
function SocialActions() {
// 1. Primer useActionState para la acción 'Me Gusta'
const [liked, likeAction] = useActionState(toggleLike, false);
// 2. Segundo useActionState para la acción 'Seguir'
const [following, followAction] = useActionState(toggleFollow, false);
return (
<div className="social-actions">
{/* Formulario para la acción 'Me Gusta' */}
<form action={likeAction}>
<button className="like-button">
{liked ? " ❤️ Me Gusta" : "♡ Me Gusta"}
</button>
</form>
{/* Formulario para la acción 'Seguir' */}
<form action={followAction}>
<button className="follow-button">
{following ? " ✔ Siguiendo" : "+ Seguir"}
</button>
</form>
</div>
);
}
export default SocialActions; El hook useActionState es una poderosa herramienta para manejar acciones asíncronas en React, permitiendo una separación clara de la lógica del cliente y del servidor, y facilitando la actualización del estado basada en los resultados de esas acciones.