React 19 de Cero a Experto / Routing
Loaders y actions: datos en la ruta
Una ruta casi nunca es solo una vista: necesita DATOS (el gasto que vas a mostrar) y a veces ESCRIBE datos (el formulario que lo guarda). React Router moderno integra esto en el propio enrutado con los LOADERS (cargan los datos ANTES de renderizar la ruta) y las ACTIONS (procesan los envíos de formularios de la ruta). Hoy ves ese modelo —que acerca React Router a cómo funciona Next.js (SC-07)— y cómo se relaciona con TanStack Query.
El plato listo antes de sentarte
Loaders: datos antes de renderizar
Asocias una función loader a una <Route>: se ejecuta ANTES de renderizar, y el componente lee el
resultado con useLoaderData:
import { createBrowserRouter, RouterProvider, useLoaderData } from "react-router-dom";
const router = createBrowserRouter([
{
path: "/gastos/:id",
element: <DetalleGasto />,
// el loader corre ANTES de renderizar; recibe los params de la ruta
loader: async ({ params }) => {
const res = await fetch(`/api/gastos/${params.id}`);
if (!res.ok) throw new Response("No encontrado", { status: 404 });
return res.json(); // lo que devuelve queda disponible en el componente
},
},
]);
function DetalleGasto() {
// los datos YA están aquí: sin useEffect, sin isLoading
const gasto = useLoaderData() as Gasto;
return <p>{gasto.nombre}: {gasto.valor}</p>;
}El loader recibe los params de la ruta (el id) y devuelve los datos; React Router los tiene listos
ANTES de montar el componente, que los lee con useLoaderData —sin useEffect, sin estado de carga—.
Si el loader lanza una Response de error, un Error Boundary de ruta la atrapa (los del módulo 5).
Fíjate que esto usa createBrowserRouter con un array de rutas (el "data router"), no el <BrowserRouter>
con JSX de las lecciones previas —el modelo de datos requiere esta API—.
Actions: escribir desde la ruta
El espejo del loader: un action procesa el envío de un <Form> de React Router (no el <form>
normal), ideal para crear/editar:
import { Form, redirect } from "react-router-dom";
const router = createBrowserRouter([
{
path: "/gastos/nuevo",
element: <NuevoGasto />,
// el action recibe el FormData del <Form> (como las Actions del módulo 4)
action: async ({ request }) => {
const datos = await request.formData();
await fetch("/api/gastos", { method: "POST", body: datos });
return redirect("/gastos"); // tras guardar, navega a la lista
},
},
]);
function NuevoGasto() {
return (
<Form method="post"> {/* al enviar, dispara el 'action' de esta ruta */}
<input name="nombre" />
<input name="valor" type="number" />
<button>Guardar</button>
</Form>
);
}El <Form method="post"> de React Router, al enviarse, llama al action de la ruta con el FormData
—igual que las Actions de React 19 del módulo 4, aquí integradas al router—. El action escribe y suele
terminar con redirect(...). Tras un action, React Router revalida automáticamente los loaders de la
página, así que la lista se actualiza sola. Es el ciclo leer-escribir-revalidar, pero orquestado por el
router en vez de por ti.
¿Qué ventaja tiene un loader de ruta frente a cargar datos con useEffect dentro del componente?
Mini-reto
Diseña la capa de datos de dos rutas (en papel o en tu proyecto): 1) /gastos/:id con un loader que
haga fetch del gasto por params.id y lance una Response 404 si no existe; el componente lo lee con
useLoaderData; 2) /gastos/nuevo con un <Form method="post"> y un action que guarde y haga
redirect("/gastos"). Explica qué ve el usuario: ¿hay un "Cargando…" al entrar al detalle? ¿qué pasa
tras enviar el formulario?
Qué sigue
Ya cargas y escribes datos por ruta. Falta una pieza crítica de toda app real: proteger rutas que
requieren estar autenticado (/perfil, /panel) para que un visitante sin sesión no entre. La próxima
lección trae las RUTAS PROTEGIDAS —con demo en vivo— y cómo redirigir al login conservando a dónde
quería ir el usuario.