CodeForge

React 19 de Cero a Experto / Routing

Loaders y actions: datos en la ruta

Teoría22 min20 XP

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:

loader.tsx
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:

action.tsx
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.