
React 19: Todo acerca de los nuevos Hooks!
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:
-
Qué problema resuelven
-
Cómo se resolvía antes
-
Cómo se resuelve ahora con React 19
-
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
yuseFormState
. -
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.