Dominando el Hook useActionState de React

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).

  Este hook centraliza la lógica de:

  1. El estado inicial.
  2. La función asíncrona que lo actualiza.
  3. El nuevo estado resultante (incluyendo mensajes de éxito o error).
  4. El estado de carga (isPending).

1Entendiendo la Sintaxis Básica

La sintaxis del useActionState es concisa y devuelve hasta tres valores clave:

js
import { useActionState } from "react";

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

Argumentos:

ArgumentoDescripción
actionFnLa función asíncrona (Action) que se ejecutará al enviar el formulario. Recibe dos argumentos: (previousState, formData). Debe devolver el nuevo estado.
initialStateEl 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.

Valores Retornados:

ValorDescripción
stateEl estado actual del componente. Inicialmente es initialState, y luego se actualiza con lo que devuelva actionFn.
formActionUna 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).

2Un Contador con Actualización de Estado Simple

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.

js
// 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;

3Manejo de Carga (isPending) y Mensajes de Retroalimentación

Usaremos 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.

js
"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;

4Uso con Server Actions (Botón 'Me Gusta')

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.

js
// --- 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;

5Múltiples Acciones Independientes

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.

js
// --- 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;
  Conclusion

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.