Lecciones del curso

Introducción al desarrollo web full stack con React Router

RRv7 como puente a React 19
5m
Todo sobre rutas
6m
Todas las piezas de un Route Module
7m
Cargando datos desde la base de datos
4m
Actions y mutaciones
7m
Componente <Link> y navegación
5m
UI Patterns: Pending & Optimistic
4m
¿Cómo sustituir un useEffect?
3m
Tipado seguro de extremo a extremo
1m
Testing con RRv7
1m
Instalación
1m
Estrategias de renderizado
3m

Todo sobre rutas con React Router Framework

Recordemos entonces que con React Router Framework tenemos a la mano el archivo routes.ts, que nos permitirá definir cualquier pattern que queramos hacer coincidir con la URL. Este archivo tiene la siguiente forma.

// ./routes.ts import { route } from "@react-router/dev/routes"; export default [ route("cursos/react-router", "./routes/detail-route.tsx"), // pattern ^ ^ módulo de ruta ]

Observa que se utiliza una función que viene desde @react-router/dev/routes. Esta función se usa dentro del array que se exporta por default del archivo y recibe dos parámetros. El primero es el pathname o pattern que hará match con la URL del navegador, y el segundo, es el path o dirección del archivo que contiene nuestro módulo de ruta donde programaremos su comportamiento. ✅

👀 Si te habían gustado definir las rutas con convenciones en el nombre del archivo y la carpeta routes, aún puedes emplearlas con @react-router/fs-routes.

Módulos de ruta

Cómo ya hemos dicho, los módulos de ruta son los archivos donde definiremos el comportamiento de cada una de nuestras rutas.

route("blog/:slug", "./blogPost.tsx"), // módulo de ruta ^^^^^^^^

Si no estás muy familiarizado con la nomenclatura de JS: un módulo no es más que el nombre que recibe un archivo .js, .ts o .tsx moderno, y que puede exportar funciones que React Router usará.

// Con la función loader cargamos datos al componente export async function loader({ params }) { return await db.post.findUnique({where:{slug:params.slug}}); } // Estos datos están disponibles como un prop export default function Component({loaderData}) { return <> <h1>{loaderData.title}</h1> <MarkDown>{loaderData.body}</MarkDown> </> }

Este es un ejemplo de un módulo de ruta muy simple, que consigue un post de blog desde la base de datos a partir del parámetro slug que viene en la URL.

El componente, ya en el cliente, tiene acceso a este post de blog a través del prop: loaderData. Dime, ¿podría ser más simple? 🤯

👀 Los módulos de ruta pueden exportar muchas más funciones útiles para nuestra app y también capturar los errores de forma super elegante, pero hablaremos más afondo de cada una de ellas en las siguientes lecciones. Sé paciente. 😌

Rutas anidadas

Trabajar con rutas anidadas puede ser muy benéfico para no repetir JSX y concentrarse en pequeños componentes, pero, aunque pequeños, al ser módulos de ruta son muy potentes. En React Router es muy fácil crear rutas que son hijas de otras rutas:

import { route, index } from "@react-router/dev/routes"; export default [ // Ruta padre (o madre 👩🏻‍🍼) route("dashboard", "./routes/dashboard.tsx", [ // rutas hijas (o módulos hijos 👶🏻) index("./routes/home.tsx"), route("orders", "./routes/orders.tsx"), // /dashboard/orders ]), ];

Toma en cuenta que la ruta con el segmento “orders” hará match con la URL /dashboard/orders, pues es hija de la ruta dashboard. Misma, que cuando la URL coincide solo con el segmento /dashboard, se renderizará la ruta hija index. 🪄 

👀 La función index() solo necesita un parámetro: el path o dirección del módulo: index("./cualquier/folder/home.tsx"). Ojo, estas rutas no pueden tener hijos. 🫃🏻

Es importante decir que estas rutas hijas son renderizadas por medio del componente <Outlet /> que su padre utilizará en el JSX. Hablaremos más de este componente en un momento. 🪆

Rutas de Layout

Hay veces que queremos que nuestros componentes se rendericen dentro de cierto JSX, como cuando existe un componente Navbar o un menú lateral que queremos reutilizar en distintas rutas o mostrar notificaciones y alertas. 🔔 Pero, no siempre necesitamos el segmento al que el anidamiento nos forzará. Es decir, hay veces que queremos el layout de “dashboard” sin el segmento o palabra “dashboard” en la ruta.

import { route, layout, index, prefix, } from "@react-router/dev/routes"; export default [ layout("./layouts/marketing-layout.tsx", [ index("./routes/home.tsx"), route("subscribe", "./routes/contact.tsx"), ]), layout("./routes/dashboard/layout.tsx", [ route("public/orders", "./routes/dashboard/public_orders.tsx"), ]), ...prefix("dashboard", [ index("./routes/dashboard/dashboard-index.tsx"), layout("./routes/dashboard/layout.tsx", [ route("orders", "./routes/dashboard/orders.tsx"), route("orders/:id", "./routes/dashboard/order-detail.tsx"), ]), ]), ];

En este ejemplo, podemos ver que se pueden usar distintos layouts para distintas regiones de nuestra aplicación web. Además, también tenemos una función más: prefix(). Prefix, nos evita repetirnos y nos ayuda a agrupar rutas sin añadir un segmento previo. Con Prefix, tenemos toda la libertad de emplear el layout que más nos convenga según la temporada o necesidades de marketing. Podemos pensar este pattern como una alternativa avanzada a la simple anidación. ¡Genial! 👍

👀 No te olvides de añadir el componente <Outlet /> también a tus componentes Layout:

// Outlet es útil para prefix y para el anidamiento común. import { Outlet } from "react-router"; export default function ProjectLayout() { return ( <article> <nav><Link>Cursos</Link><Link>Blog</Link></nav> <main> <Outlet /> // Aquí se renderizarán las rutas 🧑‍🧒‍🧒 </main> </article> ); }

Segmentos dinámicos

Los parámetros en la ruta son una herramienta esencial de este bonito framework. Estos parámetros o variables se definen de una manera muy familiar: empleando los dos puntos o el colon como le dicen los gringos. :id. 🍑

route("orders/:orderId", "./routes/order-detail.tsx"),

El nombre es: segmentos dinámicos. Y, puedes tener todos los necesarios, todos los que quieras y sin culpas. No como cuando no te dejan comer todos los tamales de la posada. 🫔

route("dashboard/:userId/services/:serviceId", "./service.tsx"),

Estos segmentos dinámicos terminan siendo llaves del objeto params que la función loader recibe. Esto cumple con las mejores convenciones del desarrollo web.

async function loader({ params }) { // ^ { userId: string; serviceId: string } }

¡Maravilloso! 😃

Segmentos opcionales

Estamos por terminar con todas las opciones que tenemos a la mano en las rutas, pero no podemos irnos sin mencionar una de las herramientas más interesantes, me refiero a los segmentos opcionales.

route(":userId?/cursos", "./cursos-o-mis-cursos.tsx"),

Agregando un ? al segmento, podemos convertir en opcional un parámetro de la ruta. Así como así. 😳

Y también podemos hacer opcionales segmentos que no son dinámicos:

route("blog/:blogSlug/share?", "./post-reader.tsx");

Esta opción es super interesante, porque nos abre un mar de posibilidades a la hora de ponernos creativos con nuestras rutas. 🌊🧜🏻‍♂️

Splats

Finalmente, los populares “catchAll” o segmentos de estrella (star segments). Que seguramente has visto en algún otro lugar. Si un segmento termina con un asterisco, hará match o coincidirá con cualquier character incluyendo cualquier otro slash (/).

route("videos/*", "./videos-proxy.tsx"), // /videos/intro.mp4 o /videos/marketing/bumper.mov

👀 const { "*": splat } = params; Se puede deconstruir en la función loader asignándole un nombre.

¡Uy! Hemos aprendido un montón sobre las nuevas rutas de React Router ¿no crees? Y nos hemos motivado con su simplificación y lo familiar que resulta su uso. 🤓

Ahora que sabemos cómo definir nuestras rutas, asignarles layout o agruparlas con prefixes, yo creo que es momento de seguir avanzando y aprender todo lo que se puede hacer dentro de un módulo de ruta. 😎 

Continuemos, no es momento de detenerse. 🏄🏻‍♂️

Enlaces relacionados

Módulos de ruta

Tim Berners-Lee, sobre params