Saltearse al contenido

React Router

React Router es una librería que permite gestionar el enrutamiento en aplicaciones React, es decir, permite el enrutamiento del lado del cliente.

El enrutamiento del lado del cliente permite que la aplicación actualice la URL desde un clic en un enlace sin hacer otra solicitud de otro documento desde el servidor. En su lugar, la aplicación puede renderizar inmediatamente una nueva interfaz de usuario y realizar solicitudes de datos con fetch para actualizar la página con nueva información (si es necesario).

Como sabemos, React cuenta con un mecanismo de control del DOM, el DOM virtual o React DOM, que permite actualizar el DOM de forma eficiente. React Router tomará ventaja de este mecanismo para actualizar la interfaz de usuario de forma eficiente, permitiendo experiencias de usuario más rápidas porque el navegador no necesita solicitar un documento completamente nuevo o reevaluar estilos CSS actuales, sino que solo necesita actualizar el DOM virtual.

Instalación

Para instalar React Router en un proyecto de React, se debe ejecutar el siguiente comando:

Terminal window
1
npm install react-router-dom

Implementación

Para implementar React Router en una aplicación de React, veremos que existen tres elementos que siempre deben estar presentes: un enrutador, un proveedor de rutas y, por supuesto, las rutas.

Enrutador

Como hemos establecido en la sección anterior, un enrutador es un componente que se encarga de gestionar las rutas de la aplicación.

React Router proporciona varios tipos de enrutadores, cada uno con un propósito específico. Por ejemplo, el enrutador HashRouter utiliza el hash de la URL para gestionar las rutas, mientras que el enrutador MemoryRouter utiliza la memoria del navegador para gestionar las rutas, lo que significa que no actualiza la URL en la barra de direcciones del navegador. Por este motivo, es importante elegir el enrutador que mejor se adapte a las necesidades de la aplicación, para lo cual recomendamos revisar la documentación oficial de React Router.

Con la aclaración anterior y, tomando en cuenta que buscamos implementar una aplicación web tradicional, utilizaremos el enrutador BrowserRouter en de aquí en adelante.

Este enrutador utiliza la URL completa del navegador para gestionar las rutas. Para esto, emplea history.pushState, la cual es una función perteneciente a la API de Historial de navegación de HTML5 que permite modificar la URL del navegador de forma programática sin recargar la página. Esto es sumamente útil en nuestro caso, ya que nos centraremos en SPA (Single Page Application) y, por lo tanto, no queremos recargar la página cada vez que el usuario navega a una nueva ruta.

Para utilizar el enrutador BrowserRouter, debemos importar y emplear la función createBrowserRouter para definir el enrutador y establecer la configuración del mismo.

1
import { createBrowserRouter } from 'react-router-dom';
2
3
const router = createBrowserRouter([
4
/* Rutas */
5
]);

Esta función permite configurar el enrutador mediante un arreglo de rutas, el cual se pasa como argumento a la función createBrowserRouter.

Alternativamente, podemos utilizar el componente BrowserRouter envolviendo las rutas de la aplicación.

1
import { BrowserRouter } from 'react-router-dom';
2
3
function App() {
4
return (
5
<BrowserRouter>
6
{/* Rutas */}
7
</BrowserRouter>
8
);
9
}

Proveedor de rutas

Para poder gestionar el renderizado de nuestros componentes de acuerdo a la ruta actual, necesitamos un componente que organice y brinde la información necesaria que ha sido configurada en el enrutador.

Entonces, el proveedor de rutas es un componente que envuelve a toda la aplicación y se encarga de gestionar las rutas de la misma.

Debido a que el enrutamiento compromete a toda la aplicación, el proveedor de rutas debe definirse en el nivel más alto de la aplicación, es decir, en el punto de entrada de la misma. Si empleamos, la estructura de proyecto de Vite, el proveedor de rutas se definirá en el archivo main.jsx.

1
import { RouterProvider } from "react-router-dom";
2
3
ReactDOM.createRoot(document.getElementById("root")).render(
4
<RouterProvider router={router} />
5
);

Como vemos, el proveedor de rutas requiere un prop router, el cual es el enrutador que hemos definido previamente.

También es importante resaltar que, al utilizar RouterProvider no es necesario utilizar el componente BrowserRouter en la aplicación, ya que RouterProvider se encarga de gestionar las rutas de la aplicación.

Al emplear este patrón, podemos tener un mayor control sobre la configuración del enrutador y, por lo tanto, podemos implementar funcionalidades avanzadas. En este sentido, el componente principal de la aplicación (App) esta implícito a través de las rutas que definimos en el enrutador.

Rutas

Las rutas son componentes que se encargan de renderizar otros componentes de acuerdo a la URL actual. Para definir una ruta, tenemos dos alternativas: emplear el componente Route o definir un objeto de configuración en el enrutador.

En cualquier caso, las rutas deben ser definidas en el enrutador y, por lo tanto, deben ser pasadas como argumento a la función createBrowserRouter o como hijos del componente BrowserRouter.

Objeto de configuración

Para definir una ruta mediante un objeto de configuración, debemos especificar dos propiedades de manera obligatoria: path y element.

  • path: Es una cadena de texto que establece una expresión regular que debe coincidir con la URL actual para que la ruta sea activada.
  • element: Es un componente que será renderizado si la URL actual coincide con la expresión regular establecida en path.
1
const router = createBrowserRouter([
2
{
3
path: "/",
4
element: <Home />
5
},
6
]);

Componente Route

De manera similar, podemos definir una ruta utilizando el componente Route e indicando las mismas propiedades que en el objeto de configuración.

No obstante, si optamos por esta alternativa, debemos tener en cuenta la manera en que se ha definido el enrutador.

  • Si hemos definido el enrutador mediante la función createBrowserRouter, necesitaremos de otra función para renderizar poder establecer las rutas. Dicha función es createRoutesFromElements.

    1
    import { createBrowserRouter, createRoutesFromElements, Route } from 'react-router-dom';
    2
    3
    const router = createBrowserRouter(
    4
    createRoutesFromElements([<Route path="/" element={<Home />} />])
    5
    );
  • Si optamos por utilizar el componente BrowserRouter, podemos definir las rutas de la siguiente manera:

    1
    import { BrowserRouter, Route } from 'react-router-dom';
    2
    3
    function App() {
    4
    return (
    5
    <BrowserRouter>
    6
    <Route path="/" element={<Home />} />
    7
    </BrowserRouter>
    8
    );
    9
    }

Reglas de enrutamiento

Las reglas de enrutamiento son las expresiones regulares que se utilizan para definir las rutas de la aplicación. Estas reglas pueden ser tan simples o complejas como se desee. A continuación, se presentan algunos ejemplos de reglas de enrutamiento comunes:

  • /: La ruta raíz de la aplicación.
  • /<path>: Una ruta que coincide con cualquier URL que comience con el valor de <path>.
  • *: Un comodín que coincide con cualquier URL que no coincida con ninguna de las rutas anteriores. Este se utiliza comúnmente para mostrar una página de error 404 cuando no se encuentra ninguna ruta coincidente.

Segmentos dinámicos

Algunas veces, es necesario definir rutas que contengan parámetros dinámicos. De esta manera, se brinda acceso a un recurso específico a través de una URL única.

Para definir un segmento dinámico, se debe utilizar el carácter : seguido de un nombre de parámetro en la propiedad path. Por ejemplo, si deseamos definir una ruta que muestre los detalles de un producto específico, podríamos definir la ruta de la siguiente manera:

1
<Route path="/products/:id" element={<ProductDetails />} />

Por supuesto, el componente relacionado con la ruta debe ser capaz de identificar cual es el recurso solicitado a través del parámetro id. Para acceder a este parámetro, React Router proporciona un hook llamado useParams. Entonces, el componente ProductDetails podría ser definido de la siguiente manera:

1
import { useParams } from 'react-router-dom';
2
3
function ProductDetails() {
4
const params = useParams();
5
6
return (
7
<div>
8
<h1>Product Details</h1>
9
<p>Product ID: {params.id}</p>
10
</div>
11
);
12
}

En este caso, el hook useParams nos permite acceder a los parámetros de la URL actual. En este caso, el parámetro id es el único parámetro definido en la ruta, por lo que podemos acceder a él directamente a través de params.id.

Clasificación de rutas

Cuando React Router debe decidir qué ruta coincide con una URL determinada, utiliza un algoritmo de clasificación para determinar la ruta más específica que coincida con la URL. Este algoritmo se basa en varios factores, como el número de segmentos, segmentos estáticos, segmentos dinámicos, comodines, etc.

Por ejemplo, considerando las siguientes rutas:

1
<Route path="/products" element={<Products />} />
2
<Route path="/products/:id" element={<ProductDetails />} />
3
<Route path="/products/new" element={<NewProduct />} />

Si la URL actual es /products/new, React Router seleccionará la tercera ruta, ya que es la más específica que coincide con la URL. Si la URL actual es /products/nuevo, React Router seleccionará la segunda ruta, ya que es la más específica que coincide con la URL. Dado que el segmento :id es un segmento dinámico, puede coincidir con cualquier valor, excepto con segmentos estáticos pertenecientes a otras rutas (en este caso, new).

Rutas anidadas

Las rutas anidadas son rutas que se encuentran dentro de otras rutas. Este patrón es común en aplicaciones web, ya que permite organizar las rutas de manera jerárquica y modular. React Router emplea un enfoque inspirado en el sistema de enrutamiento de Ember.js para definir rutas anidadas. En este enfoque, los segmentos de la URL determinan:

  • Los layouts o esquemas de diseño que se deben renderizar en la página.
  • Las dependencias de datos de esos layouts.

Por ejemplo, si consideramos que cualquier ruta que definamos deberá contar con la misma barra de navegación, encontraremos una dependencia hacia dicho componente.

1
function Home() {
2
return (
3
<div>
4
<Navbar />
5
<h1>Home</h1>
6
</div>
7
);
8
}
9
10
function Products() {
11
return (
12
<div>
13
<Navbar />
14
<h1>Products</h1>
15
</div>
16
);
17
}
18
19
function Contact() {
20
return (
21
<div>
22
<Navbar />
23
<h1>Contact</h1>
24
</div>
25
);
26
}

En este caso, el componente Navbar es una dependencia de los componentes Home, Products y Contact.

Por este motivo, React Router opta por definir rutas anidadas mediante el componente Outlet. Este componente se utiliza para renderizar rutas anidadas dentro de una ruta principal. Para esto, se debe definir un componente que contenga el componente Outlet y las rutas anidadas, es decir, un componente que actúe como un layout o esquema de diseño.

1
function NavbarLayout() {
2
return (
3
<div>
4
<Navbar />
5
<Outlet />
6
</div>
7
);
8
}

Al contar con este componente, podemos definir las rutas de manera jerárquica mediante la propiedad children de cada ruta.

1
{
2
element: <NavbarLayout />,
3
children: [
4
{
5
path: "/",
6
element: <Home />
7
},
8
{
9
path: "/products",
10
element: <Products />
11
},
12
{
13
path: "/contact",
14
element: <Contact />
15
}
16
]
17
}

De esta manera, React Router renderizará el componente NavbarLayout y, a su vez, renderizará el componente correspondiente a la ruta actual en el componente Outlet.

Así mismo, podemos continuar anidando rutas de manera jerárquica, lo que nos permite organizar nuestra aplicación de manera modular y escalable. Esta práctica es común cuando tenemos rutas que comparten un mismo layout, un mismo contexto o, porque un recurso requiere un nivel de especificidad mayor. Este último caso puede ejemplificarse con la siguiente estructura de rutas:

1
{
2
element: <NavbarLayout />,
3
children: [
4
{
5
path: "/",
6
element: <Home />
7
},
8
{
9
path: "/products",
10
children: [
11
{
12
index: true,
13
element: <Products />
14
},
15
{
16
path: "/:id",
17
element: <ProductDetails />
18
},
19
{
20
path: "/new",
21
element: <ProductForm />
22
},
23
{
24
path: "/edit/:id",
25
element: <ProductForm />
26
},
27
]
28
},
29
...
30
]
31
}

En este caso, la ruta /products es la ruta principal y, por lo tanto, se renderizará el componente Products en el componente Outlet. A su vez, las rutas anidadas de /products se renderizarán en base al componente Products.

Bibliografía