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
React Server Components
60m

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