React 19: Todo acerca de los nuevos Hooks!

React 19: Todo acerca de los nuevos Hooks!

Artículospor Luis

React 19 marca un hito importante con la introducción de cinco nuevos hooks diseñados para mejorar la gestión de formularios, el manejo del estado en componentes asincrónicos y la experiencia de desarrollo en general. Estos hooks nos permiten escribir código más limpio, declarativo y reactivo.

En este artículo analizaremos uno por uno:

  1. Qué problema resuelven

  2. Cómo se resolvía antes

  3. Cómo se resuelve ahora con React 19

  4. Casos reales con ejemplos de código comentado en español

useOptimistic

Qué resuelve

Permite aplicar actualizaciones optimistas en la UI: mostrar un cambio antes de que la operación se confirme en el servidor. Ideal para UX inmediata.

Antes (React 18 y anteriores)

"use client";

import { useState } from "react";

export default function TodoList() {
  const [tasks, setTasks] = useState<string[]>([]);
  const [loading, setLoading] = useState(false);

  const addTask = async (task: string) => {
    const tempTask = `⌛ ${task}`;
    setTasks((prev) => [...prev, tempTask]);
    setLoading(true);

    try {
      await new Promise((r) => setTimeout(r, 1000));
      setTasks((prev) => [...prev.filter(t => t !== tempTask), task]);
    } catch (err) {
      alert("Error");
      setTasks((prev) => prev.filter(t => t !== tempTask));
    } finally {
      setLoading(false);
    }
  };

  return (
    <div>
      <ul>{tasks.map((t, i) => <li key={i}>{t}</li>)}</ul>
      <button onClick={() => addTask("Nueva tarea")} disabled={loading}>
        {loading ? "Agregando..." : "Agregar"}
      </button>
    </div>
  );
}

Ahora con useOptimistic

"use client";

import { useState, useOptimistic } from "react";

export default function TodoListOptimistic() {
  const [tasks, setTasks] = useState<string[]>([]);
  const [optimisticTasks, addOptimisticTask] = useOptimistic(
    tasks,
    (current, newTask: string) => [...current, `⌛ ${newTask}`]
  );

  async function handleAdd(task: string) {
    addOptimisticTask(task); // UI inmediata
    try {
      await new Promise((r) => setTimeout(r, 1000));
      setTasks((prev) => [...prev, task]);
    } catch {
      alert("Fallo en el backend");
    }
  }

  return (
    <div>
      <ul>{optimisticTasks.map((t, i) => <li key={i}>{t}</li>)}</ul>
      <button onClick={() => handleAdd("React 19")}>Agregar</button>
    </div>
  );
}

Ventajas

  • Menos líneas de código

  • Menor estado a manejar

  • Flujo limpio y desacoplado

  • Sin riesgo de olvidar revertir el estado

useFormStatus

Qué resuelve

Este hook permite acceder al estado del formulario (pending, submitted, error) sin necesidad de pasar props manualmente. Se usa dentro de un <form> con action.

Antes

"use client";

import { useState } from "react";

function SubmitButton({ loading }: { loading: boolean }) {
  return <button disabled={loading}>{loading ? "Enviando..." : "Enviar"}</button>;
}

export default function ContactForm() {
  const [loading, setLoading] = useState(false);

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    setLoading(true);
    await new Promise((r) => setTimeout(r, 1000));
    setLoading(false);
  };

  return (
    <form onSubmit={handleSubmit}>
      <textarea name="msg" />
      <SubmitButton loading={loading} />
    </form>
  );
}

Ahora con useFormStatus (React 19)

"use client";

import { useFormStatus } from "react-dom";

function SubmitButton() {
  const { pending } = useFormStatus();
  return <button disabled={pending}>{pending ? "Enviando..." : "Enviar"}</button>;
}

export default function ContactForm() {
  async function handleSubmit(formData: FormData) {
    await new Promise((r) => setTimeout(r, 1000));
    console.log("Mensaje:", formData.get("msg"));
  }

  return (
    <form action={handleSubmit}>
      <textarea name="msg" />
      <SubmitButton />
    </form>
  );
}

Ventajas

  • El botón conoce el estado sin necesidad de props

  • Composición más limpia

  • Ideal para formularios reutilizables o componentizados

useFormState

Qué resuelve

useFormState permite manejar el estado de un formulario basado en el resultado de una action, típicamente una server action. Es útil cuando queremos mostrar mensajes de éxito o error sin tener que controlar el estado manualmente en el cliente.

Antes

"use client";

import { useState } from "react";

export default function FeedbackForm() {
  const [statusMsg, setStatusMsg] = useState("");

  const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    const formData = new FormData(e.currentTarget);
    await new Promise((r) => setTimeout(r, 1000));
    setStatusMsg(`Gracias, ${formData.get("name")}`);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input name="name" required />
      <button type="submit">Enviar</button>
      {statusMsg && <p>{statusMsg}</p>}
    </form>
  );
}

Ahora con useFormState

"use client";

import { useFormState } from "react-dom";

// Función del lado servidor (puede ir en el mismo archivo o en uno separado)
async function submitForm(_: string, formData: FormData) {
  const name = formData.get("name")?.toString() ?? "";
  await new Promise((r) => setTimeout(r, 1000));
  return `Gracias, ${name}`;
}

export default function FeedbackForm() {
  const [message, formAction] = useFormState(submitForm, "");

  return (
    <form action={formAction}>
      <input name="name" required />
      <button type="submit">Enviar</button>
      {message && <p>{message}</p>}
    </form>
  );
}

Ventajas

  • No necesitas manejar useState para el mensaje.

  • El estado viene directamente de la action.

  • Reduce la lógica en el cliente.

useActionState

Qué resuelve

useActionState es similar a useFormState, pero da más control: puedes manejar el estado del formulario, errores y respuesta, todo desde una acción del servidor. Es perfecto para flujos con validaciones o mutaciones complejas.

Antes (validación manual)

"use client";

import { useState } from "react";

export default function LoginForm() {
  const [error, setError] = useState("");

  const handleLogin = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    const formData = new FormData(e.currentTarget);
    const user = formData.get("user")?.toString();
    const pass = formData.get("pass")?.toString();

    if (!user || !pass) {
      setError("Faltan datos");
      return;
    }

    await new Promise((r) => setTimeout(r, 1000));
    if (user !== "admin" || pass !== "1234") {
      setError("Credenciales inválidas");
    } else {
      setError("");
      alert("Login exitoso");
    }
  };

  return (
    <form onSubmit={handleLogin}>
      <input name="user" placeholder="Usuario" />
      <input name="pass" type="password" placeholder="Contraseña" />
      <button>Iniciar sesión</button>
      {error && <p>{error}</p>}
    </form>
  );
}

Ahora con useActionState (React 19)

"use client";

import { useActionState } from "react";

async function loginAction(
  prevState: { error?: string },
  formData: FormData
) {
  const user = formData.get("user")?.toString();
  const pass = formData.get("pass")?.toString();

  if (!user || !pass) {
    return { error: "Completa ambos campos" };
  }

  await new Promise((r) => setTimeout(r, 1000));

  if (user !== "admin" || pass !== "1234") {
    return { error: "Credenciales inválidas" };
  }

  return { error: undefined };
}

export default function LoginForm() {
  const [state, formAction] = useActionState(loginAction, { error: undefined });

  return (
    <form action={formAction}>
      <input name="user" placeholder="Usuario" />
      <input name="pass" type="password" />
      <button type="submit">Iniciar sesión</button>
      {state.error && <p>{state.error}</p>}
    </form>
  );
}

Ventajas

  • Toda la lógica de validación se mueve al servidor.

  • El estado de error es limpio y gestionado por React.

  • Facilita construir flujos seguros y controlados.

use

Qué resuelve

use() permite leer el resultado de una promesa directamente desde un componente sin useEffect, ni estados intermedios. Es ideal cuando estás dentro de un React Server Component (RSC) y necesitas datos asincrónicos.

Antes

// Client component con useEffect
"use client";
import { useEffect, useState } from "react";

export default function UserInfo() {
  const [user, setUser] = useState<{ name: string } | null>(null);

  useEffect(() => {
    fetch("/api/user")
      .then(res => res.json())
      .then(setUser);
  }, []);

  if (!user) return <p>Cargando...</p>;
  return <p>Hola, {user.name}</p>;
}

Ahora con use (React Server Components)

import { use } from "react";

// Función asincrónica que retorna datos
async function getUser() {
  return { name: "Luis Velito" };
}

export default function UserInfo() {
  const user = use(getUser()); // Promesa directamente
  return <p>Hola, {user.name}</p>;
}

Ventajas

  • Código más limpio

  • Sin estados ni efectos para datos asincrónicos

  • Ideal en SSR (Server-Side Rendering)

Conclusión

React 19 trae una nueva era de ergonomía y potencia para el desarrollo frontend. Estos hooks no solo reducen el código repetitivo, sino que también nos acercan a una experiencia más declarativa y reactiva, con menos errores y una mejor separación de responsabilidades.

¿Qué deberías probar hoy?

  • Refactoriza tus formularios con useFormStatus y useFormState.

  • Usa useOptimistic en interacciones frecuentes como likes, comentarios o toggle de favoritos.

  • Si haces validaciones, migra a useActionState.

  • Si estás usando Server Components, empieza a adoptar use() en llamadas de datos.

 

-- ¿Quieres recibir mi newsletter? --

No te pierdas de aprender:

Mi newsletter mensual viene con una dosis de inspiración, recursos para descargar, consejos de desarrollo rápidos y los mismos recursos que aprendo.