React Router v6 Una Guía para Principiantes — CodesCode

Aprende cómo navegar a través de una aplicación de React con múltiples vistas con React Router, la biblioteca de enrutamiento estándar de facto para React.

React Router es la biblioteca de enrutamiento estándar de facto para React. Cuando necesitas navegar a través de una aplicación de React con múltiples vistas, necesitarás un enrutador para gestionar las URL. React Router se encarga de eso, manteniendo tu interfaz de usuario de la aplicación y la URL sincronizadas.

Este tutorial te presenta React Router v6 y muchas cosas que puedes hacer con él.

Introducción

React es una popular biblioteca de JavaScript para construir aplicaciones web interactivas que pueden ofrecer contenido dinámico. Estas aplicaciones pueden tener múltiples vistas (también conocidas como páginas), pero a diferencia de las aplicaciones de varias páginas convencionales, navegar a través de estas vistas no debería provocar que se recargue toda la página. En su lugar, las vistas se representan dentro de la página actual.

El usuario final, que está acostumbrado a las aplicaciones de varias páginas, espera que estén presentes las siguientes características:

  • Cada vista debería tener una URL que especifique de manera única esa vista. Esto es para que el usuario pueda guardar la URL como referencia en otro momento, por ejemplo, www.example.com/productos.
  • Los botones de retroceso y avance del navegador deben funcionar como se espera.
  • Las vistas anidadas generadas dinámicamente también deberían tener una URL propia, como por ejemplo example.com/productos/zapatos/101, donde 101 es el ID del producto.

El enrutamiento es el proceso de mantener la URL del navegador sincronizada con lo que se representa en la página. React Router te permite manejar el enrutamiento de forma declarativa. El enfoque declarativo del enrutamiento te permite controlar el flujo de datos en tu aplicación, diciendo “la ruta debe verse así”:

<Route path="/acerca-de" element={<AcercaDe />} />

Puedes colocar tu componente <Route> donde quieras que se represente tu ruta. Dado que <Route>, <Link> y todas las demás APIs con las que trabajaremos son simplemente componentes, puedes comenzar a usar el enrutamiento en React fácilmente.

Nota: hay un error común en pensar que React Router es una solución de enrutamiento oficial desarrollada por Facebook. En realidad, es una biblioteca de terceros desarrollada y mantenida por Remix Software.

Resumen

Este tutorial se divide en diferentes secciones. Primero, configuraremos React y React Router usando npm. Luego, nos sumergiremos en algunos conceptos básicos. Encontrarás diferentes demostraciones de código de React Router en acción. Los ejemplos cubiertos en este tutorial incluyen:

  • enrutamiento de navegación básico
  • enrutamiento anidado
  • enrutamiento anidado con parámetros de ruta
  • enrutamiento protegido

Todos los conceptos relacionados con la construcción de estas rutas se discutirán a lo largo del camino.

Todo el código del proyecto está disponible en este repositorio de GitHub.

¡Empecemos!

Configuración de React Router

Para seguir este tutorial, necesitarás una versión reciente de Node instalada en tu PC. Si este no es el caso, dirígete a la página de inicio de Node y descarga los binarios correctos para tu sistema. Alternativamente, puedes considerar usar un administrador de versiones para instalar Node. Tenemos un tutorial sobre cómo usar un administrador de versiones aquí.

Node viene con npm, un administrador de paquetes para JavaScript, con el cual vamos a instalar algunas de las bibliotecas que utilizaremos. Puedes obtener más información sobre cómo usar npm aquí.

Puedes verificar que ambos estén instalados correctamente emitiendo los siguientes comandos desde la línea de comandos:

node -v> 20.9.0npm -v> 10.1.0

Con eso hecho, comencemos creando un nuevo proyecto de React con la herramienta Create React App. Puedes instalar esto globalmente o usar npx, de la siguiente manera:

npx create-react-app react-router-demo

Cuando esto haya terminado, entra al directorio recién creado:

cd react-router-demo

La biblioteca consta de tres paquetes: react-router, react-router-dom y react-router-native. El paquete principal para el enrutador es react-router, mientras que los otros dos son específicos del entorno. Debes usar react-router-dom si estás construyendo una aplicación web y react-router-native si estás en un entorno de desarrollo de aplicaciones móviles utilizando React Native.

Usa npm para instalar el paquete react-router-dom:

npm install react-router-dom

Luego inicia el servidor de desarrollo con esto:

npm run start

¡Felicidades! Ahora tienes una aplicación de React funcional con React Router instalado. Puedes ver la aplicación ejecutándose en http://localhost:3000/.

Conceptos básicos de React Router

Ahora familiaricémonos con una configuración básica. Para hacer esto, crearemos una aplicación con tres vistas separadas: Inicio, Categoría y Productos.

El componente Router

Lo primero que debemos hacer es envolver nuestro componente <App> en un componente <Router> (proporcionado por React Router). Hay varios tipos de enrutadores disponibles, pero en nuestro caso, hay dos que merecen consideración:

La diferencia principal entre ellos es evidente en las URL que crean:

// <BrowserRouter>https://example.com/about// <HashRouter>https://example.com/#/about

El <BrowserRouter> se usa comúnmente, ya que aprovecha la API de historial de HTML5 para sincronizar tu interfaz de usuario con la URL, ofreciendo una estructura de URL más limpia sin fragmentos de hash. Por otro lado, el <HashRouter> utiliza la parte de hash de la URL (window.location.hash) para gestionar el enrutamiento, lo cual puede ser beneficioso en entornos donde no es posible la configuración del servidor o cuando se brinda soporte a navegadores heredados que carecen de soporte para API de historial de HTML5. Puedes leer más sobre las diferencias aquí.

También hay cuatro nuevos routers, que admiten varias nuevas APIs de datos, que se introdujeron en una versión reciente de React Router (v6.4). En este tutorial, nos centraremos en los routers tradicionales, ya que son robustos, están bien documentados y se utilizan en una gran cantidad de proyectos en Internet. Sin embargo, profundizaremos en las novedades de la versión v6.4 en una sección posterior.

Entonces, importemos el componente <BrowserRouter> y envolviéndolo alrededor del componente <App>. Cambia index.js para que se vea así:

// src/index.jsimport React from 'react';import ReactDOM from 'react-dom/client';import App from './App';import { BrowserRouter } from 'react-router-dom';const root = ReactDOM.createRoot(document.getElementById('root'));root.render(  <React.StrictMode>    <BrowserRouter>      <App />    </BrowserRouter>  </React.StrictMode>);

Este código crea una instancia de history para todo nuestro componente <App>. Veamos qué significa esto.

Un poco de historia

La biblioteca history te permite administrar fácilmente el historial de sesión en cualquier lugar donde se ejecute JavaScript. Un objeto history abstrae las diferencias en distintos entornos y proporciona una API mínima que te permite gestionar la pila del historial, navegar y persistir el estado entre sesiones. — remix-run

Cada componente <Router> crea un objeto history que realiza un seguimiento de las ubicaciones actuales y anteriores en una pila. Cuando cambia la ubicación actual, se vuelve a renderizar la vista y tienes una sensación de navegación.

¿Cómo cambia la ubicación actual? En React Router v6, el useNavigate hook proporciona una función navigate que se puede utilizar para este propósito. La función navigate se invoca cuando haces clic en un componente <Link>, y también se puede utilizar para reemplazar la ubicación actual pasando un objeto de opciones con una propiedad replace: true.

Otros métodos, como navigate(-1) para retroceder y navigate(1) para avanzar, se utilizan para navegar a través de la pila del historial y retroceder o avanzar una página.

Las aplicaciones no necesitan crear sus propios objetos history; esta tarea la gestiona el componente <Router>. En resumen, crea un objeto history, se suscribe a los cambios en la pila y modifica su estado cuando la URL cambia. Esto provoca un nuevo renderizado de la aplicación, asegurando que se muestre la interfaz de usuario correspondiente.

Continuando, tenemos enlaces y rutas.

Link y componentes de Route

El componente <Route> es el componente más importante en React Router. Renderiza una interfaz de usuario si la ubicación coincide con la ruta actual. Idealmente, un componente <Route> debe tener una prop llamada path, y si el nombre de la ruta coincide con la ubicación actual, se renderiza.

El componente <Link>, por otro lado, se utiliza para navegar entre páginas. Es comparable al elemento ancla de HTML. Sin embargo, el uso de enlaces de ancla resultaría en una actualización completa de la página, lo cual no queremos. En su lugar, podemos usar <Link> para navegar a una URL específica y hacer que la vista se vuelva a renderizar sin una actualización.

Ahora hemos cubierto todo lo que necesitas para que nuestra aplicación funcione. Borra todos los archivos excepto index.js y App.js de la carpeta src del proyecto y luego actualiza App.js de la siguiente manera:

import { Link, Route, Routes } from 'react-router-dom';const Home = () => (  <div>    <h2>Inicio</h2>    <p>¡Bienvenido a nuestra página de inicio!</p>  </div>);const Categorías = () => (  <div>    <h2>Categorías</h2>    <p>Explora los artículos por categoría.</p>  </div>);const Productos = () => (  <div>    <h2>Productos</h2>    <p>Explora los productos individuales.</p>  </div>);export default function App() {  return (    <div>      <nav>        <ul>          <li>            <Link to="/">Inicio</Link>          </li>          <li>            <Link to="/categorias">Categorías</Link>          </li>          <li>            <Link to="/productos">Productos</Link>          </li>        </ul>      </nav>      <Routes>        <Route path="/" element={<Inicio />} />        <Route path="/categorias" element={<Categorías />} />        <Route path="/productos" element={<Productos />} />      </Routes>    </div>  );}

Aquí hemos declarado tres componentes: <Home>, <Categories>, y <Products>, los cuales representan diferentes páginas en nuestra aplicación. Los componentes <Routes> y <Route> importados de React Router son utilizados para definir la lógica de enrutamiento.

En el componente <App> tenemos un menú de navegación básico, donde cada elemento es un componente <Link> de React Router. Los componentes <Link> se utilizan para crear enlaces navegables a diferentes partes de la aplicación, cada uno asociado con una ruta específica (/, /categories y /products respectivamente). Ten en cuenta que en una aplicación más grande, este menú podría encapsularse dentro de un componente de diseño para mantener una estructura consistente en diferentes vistas. También puedes agregar algún tipo de clase activa (por ejemplo, usando un componente NavLink) al elemento de navegación actualmente seleccionado. Sin embargo, para mantener el enfoque, omitiremos esto aquí.

Debajo del menú de navegación, el componente <Routes> se utiliza como un contenedor para una colección de componentes individuales <Route>. Cada componente <Route> se asocia con una ruta específica y un componente React para renderizar cuando la ruta coincide con la URL actual. Por ejemplo, cuando la URL es /categories, se renderiza el componente <Categories>.

Nota: en versiones anteriores de React Router, / coincidiría tanto con / como con /categories, lo que significaba que ambos componentes se renderizaban. La solución a esto habría sido pasar la propiedad exact al componente <Route>, asegurando que solo se coincida con la ruta exacta. Este comportamiento cambió en la v6, por lo que ahora todas las rutas coinciden exactamente de forma predeterminada. Como veremos en la siguiente sección, si deseas que coincida con más de la URL porque tienes rutas hijas, utiliza un asterisco al final, como por ejemplo <Route path="categories/*" ...>.

Si estás siguiendo junto, antes de continuar, tómate un momento para hacer clic en la aplicación y asegúrate de que todo funcione como se espera.

Enrutamiento Anidado

Las rutas de nivel superior están bien, pero tarde o temprano la mayoría de las aplicaciones necesitarán poder anidar rutas, por ejemplo, para mostrar un producto en particular o editar un usuario específico.

En React Router v6, las rutas se anidan colocando componentes <Route> dentro de otros componentes <Route> en el código JSX. De esta manera, los componentes <Route> anidados reflejan naturalmente la estructura anidada de las URL que representan.

Veamos cómo podemos implementar esto en nuestra aplicación. Cambia App.js, de la siguiente manera (donde ... indica que el código anterior permanece sin cambios):

import { Link, Route, Routes } from 'react-router-dom';import { Categories, Desktops, Laptops } from './Categories';const Home = () => ( ... );const Products = () => ( ... );export default function App() {  return (    <div>      <nav>...</nav>      <Routes>        <Route path="/" element={<Home />} />        <Route path="/categories/" element={<Categories />}>          <Route path="desktops" element={<Desktops />} />          <Route path="laptops" element={<Laptops />} />        </Route>        <Route path="/products" element={<Products />} />      </Routes>    </div>  );}

Como puedes ver, hemos movido el componente <Categories> a su propia página y ahora estamos importando dos componentes adicionales, es decir, <Desktops> y <Laptops>.

También hemos realizado algunos cambios en el componente <Routes>, que veremos en un momento.

Primero, crea un archivo Categories.js en la misma carpeta que tu archivo App.js. Luego agrega el siguiente código:

// src/Categories.jsimport { Link, Outlet } from 'react-router-dom';export const Categories = () => (  <div>    <h2>Categorías</h2>    <p>Navega por elementos por categoría.</p>    <nav>      <ul>        <li>          <Link to="desktops">Escritorios</Link>        </li>        <li>          <Link to="laptops">Laptops</Link>        </li>      </ul>    </nav>    <Outlet />  </div>);export const Desktops = () => <h3>Página de computadoras de escritorio</h3>;export const Laptops = () => <h3>Página de laptops</h3>;

Refresca tu aplicación (esto debería ocurrir automáticamente si el servidor de desarrollo está en ejecución) y luego haz clic en el enlace de Categorías. Ahora deberías ver dos nuevos puntos de menú (Escritorios y Laptops) y al hacer clic en cualquiera de ellos se mostrará una nueva página dentro de la página original de Categorías.

Entonces, ¿qué acabamos de hacer?

En App.js cambiamos nuestra ruta /categories para que se vea así:

<Route path="/categories/" element={<Categories />}>  <Route path="desktops" element={<Desktops />} />  <Route path="laptops" element={<Laptops />} /></Route>

En el código actualizado, el componente <Route> para /categories ha sido modificado para incluir dos componentes <Route> anidados dentro de él — uno para /categories/desktops y otro para /categories/laptops. Este cambio ilustra cómo React Router permite la composición con su configuración de rutas.

Al anidar componentes <Route> dentro del <Route> de /categories, podemos crear una jerarquía de URL y UI más estructurada. De esta manera, cuando un usuario navega hacia /categories/desktops o /categories/laptops, el componente <Desktops> o <Laptops> respectivamente se renderizará dentro del componente <Categories>, mostrando una clara relación padre-hijo entre las rutas y los componentes.

Nota: la ruta de una ruta anidada se compone automáticamente concatenando las rutas de sus ancestros con su propia ruta.

También hemos alterado nuestro componente <Categories> para incluir un <Outlet>:

export const Categories = () => (  <div>    <h2>Categorías</h2>    ...    <Outlet />  </div>);

Un <Outlet> se coloca en elementos de ruta padre para renderizar sus elementos de ruta hijo. Esto permite que UI anidada aparezca cuando se renderizan las rutas hijas.

Este enfoque de composición hace que la configuración de rutas sea más declarativa y fácil de entender, alineándose bien con la arquitectura basada en componentes de React.

Accediendo a las propiedades del enrutador con Hooks

En versiones anteriores, ciertas props se pasaban implícitamente a un componente. Por ejemplo:

const Home = (props) => {  console.log(props);  return ( <h2>Home</h2> );};

El código anterior mostraría lo siguiente:

{  history: { ... },  location: { ... },  match: { ... }}

En React Router versión 6, el enfoque para pasar props de enrutador ha cambiado para proporcionar un método más explícito y basado en hooks. Las props de enrutador history, location y match ya no se pasan implícitamente a un componente. En su lugar, se proporciona un conjunto de hooks para acceder a esta información.

Por ejemplo, para acceder al objeto location, debes usar el hook useLocation. El hook useMatch devuelve datos de coincidencia sobre una ruta en el path dado. El objeto history ya no está explícitamente disponible, en su lugar el hook useNavigate devolverá una función que te permite navegar programáticamente.

Hay muchos más hooks para explorar y en lugar de enumerarlos todos aquí, te animaría a que revises la documentación oficial, donde los hooks disponibles se encuentran en la barra lateral izquierda.

A continuación, vamos a ver uno de esos hooks con más detalle y hacer nuestro ejemplo anterior más dinámico.

Enrutamiento Dinámico Anidado

Para comenzar, cambia las rutas en App.js, de la siguiente manera:

<Routes>  <Route path="/" element={<Home />} />  <Route path="/categories/" element={<Categories />}>    <Route path="desktops" element={<Desktops />} />    <Route path="laptops" element={<Laptops />} />  </Route>  <Route path="/products/*" element={<Products />} /></Routes>

Los más observadores entre ustedes se darán cuenta de que ahora hay un /* al final de la ruta /products. En React Router versión 6, el /* es una forma de indicar que el componente <Products> puede tener rutas secundarias, y es un marcador de posición para cualquier segmento de path adicional que pueda seguir a /products en la URL. De esta manera, cuando navegues a una URL como /products/laptops, el componente <Products> seguirá siendo coincidente y se renderizará, y podrá procesar aún más la parte laptops del path utilizando sus propios elementos anidados <Route>.

A continuación, mueve el componente <Products> a su propio archivo:

// src/App.js...import Products from './Products';const Home = () => ( ... );export default function App() { ... }

Finalmente, crea un archivo Products.js y agrega el siguiente código:

// src/Products.jsimport { Route, Routes, Link, useParams } from 'react-router-dom';const Item = () => {  const { name } = useParams();  return (    <div>      <h3>{name}</h3>      <p>Detalles del producto para el/la {name}</p>    </div>  );};const Products = () => (  <div>    <h2>Productos</h2>    <p>Explora productos individuales.</p>    <nav>      <ul>        <li>          <Link to="dell-optiplex-3900">Dell OptiPlex 3090</Link>        </li>        <li>          <Link to="lenovo-thinkpad-x1">Lenovo ThinkPad X1</Link>        </li>      </ul>    </nav>    <Routes>      <Route path=":name" element={<Item />} />    </Routes>  </div>);export default Products;

Aquí hemos agregado una <Route> a un componente <Item> (declarado en la parte superior de la página). El path de la ruta se establece en :name, que hará coincidir cualquier segmento de path que siga a su ruta principal y pasará ese segmento como un parámetro llamado name al componente <Item>.

Dentro del componente <Item>, estamos usando el gancho useParams. Esto devuelve un objeto de pares clave/valor de los parámetros dinámicos de la URL actual. Si lo registráramos en la consola para la ruta /products/laptops, veríamos:

Objeto { "*": "laptops", nombre: "laptops" }

Luego podemos usar destructuración de objetos para obtener este parámetro directamente y luego mostrarlo dentro de una etiqueta <h3>.

¡Pruébalo! Como verás, el componente <Item> captura cualquier enlace que declares en tu barra de navegación y crea una página de forma dinámica.

También puedes probar agregando algunos elementos más al menú:

<li>  <Link to="cyberpowerpc-gamer-xtreme">CyberPowerPC Gamer Xtreme</Link></li>

Nuestra aplicación tendrá en cuenta estas nuevas páginas.

Este método de capturar segmentos dinámicos de la URL y usarlos como parámetros dentro de nuestros componentes permite una navegación y representación de componentes más flexible según la estructura de la URL.

Continuemos construyendo sobre esto en la próxima sección.

Navegación anidada con parámetros de ruta

En una aplicación del mundo real, un enrutador tendrá que lidiar con datos y mostrarlos de forma dinámica. Supongamos que tenemos algunos datos de productos devueltos por una API en el siguiente formato:

const datosProducto = [  {    id: 1,    nombre: "Dell OptiPlex 3090",    descripción:      "El Dell OptiPlex 3090 es una PC de escritorio compacta que ofrece características versátiles para satisfacer tus necesidades comerciales.",    estado: "Disponible",  },  {    id: 2,    nombre: "Lenovo ThinkPad X1 Carbon",    descripción:      "Diseñado con una construcción elegante y duradera, el Lenovo ThinkPad X1 Carbon es un portátil de alto rendimiento ideal para profesionales en movimiento.",    estado: "Agotado",  },  {    id: 3,    nombre: "CyberPowerPC Gamer Xtreme",    descripción:      "El CyberPowerPC Gamer Xtreme es un escritorio de juegos de alto rendimiento con potentes capacidades de procesamiento y gráficos para una experiencia de juego perfecta.",    estado: "Disponible",  },  {    id: 4,    nombre: "Apple MacBook Air",    descripción:      "El Apple MacBook Air es un portátil ligero y compacto con una pantalla Retina de alta resolución y potentes capacidades de procesamiento.",    estado: "Agotado",  },];

También supongamos que necesitamos rutas para las siguientes rutas:

  • /productos: esto debería mostrar una lista de productos.
  • /productos/:productId: si existe un producto con el :productId, debería mostrar los datos del producto, y si no, debería mostrar un mensaje de error.

Reemplaza el contenido actual de Productos.js con el siguiente (asegúrate de copiar el los datos de productos de arriba):

import { Link, Route, Routes } from "react-router-dom";import Producto from "./Producto";const datosProducto = [ ... ];const Productos = () => {  const listaEnlaces = datosProducto.map((producto) => {    return (      <li key={producto.id}>        <Link to={`${producto.id}`}>{producto.nombre}</Link>      </li>    );  });  return (    <div>      <h3>Productos</h3>      <p>Explora productos individuales.</p>      <ul>{listaEnlaces}</ul>      <Routes>        <Route path=":productId" element={<Producto datos={datosProducto} />} />        <Route index element={<p>Por favor selecciona un producto.</p>} />      </Routes>    </div>  );};export default Productos;

Dentro del componente, construimos una lista de componentes <Link> usando la propiedad id de cada uno de nuestros productos. Almacenamos esto en una variable listaEnlaces, antes de mostrarlo en la página.

A continuación vienen dos componentes <Route>. El primero tiene una propiedad path con el valor :productId, que (como vimos anteriormente) es un parámetro de ruta. Esto nos permite capturar y utilizar el valor de la URL en este segmento como productId. La propiedad element de este componente <Route> está configurada para renderear un componente <Product>, pasándole el array productData como propiedad. Cada vez que la URL coincide con el patrón, se renderizará este componente <Product>, con el respectivo productId capturado de la URL.

El segundo componente <Route> utiliza una propiedad de índice para renderizar el texto “Por favor selecciona un producto” cada vez que la URL coincide exactamente con la ruta base. La propiedad index indica que esta ruta es la ruta base o “índice” dentro de la configuración de estos componentes <Routes>. Entonces, cuando la URL coincide con la ruta base, es decir /products, se mostrará este mensaje.

Ahora, aquí está el código para el componente <Product> al que nos referimos arriba. Necesitarás crear este archivo en src/Product.js:

import { useParams } from 'react-router-dom';const Product = ({ data }) => {  const { productId } = useParams();  const product = data.find((p) => p.id === Number(productId));  return (    <div>      {product ? (        <div>          <h3> {product.name} </h3>          <p>{product.description}</p>          <hr />          <h4>{product.status}</h4>        </div>      ) : (        <h2>Lo siento. El producto no existe.</h2>      )}    </div>  );};export default Product;

Aquí estamos utilizando el hook useParams para acceder a las partes dinámicas de la URL como pares de clave/valor. Nuevamente, utilizamos la destructuración para obtener los datos que nos interesan (el productId).

El método find se utiliza en el array data para buscar y devolver el primer elemento cuya propiedad id coincida con el productId obtenido de los parámetros de la URL.

Ahora, cuando visites la aplicación en el navegador y seleccionas Productos, verás un submenú renderizado, que a su vez muestra los datos del producto.

Antes de seguir adelante, juega un poco con la demostración. Asegúrate de que todo funcione y de que entiendes lo que está ocurriendo en el código.

Protegiendo Rutas

Un requisito común para muchas aplicaciones web modernas es asegurarse de que solo los usuarios registrados puedan acceder a ciertas partes del sitio. En esta próxima sección, veremos cómo implementar una ruta protegida, de manera que si alguien intenta acceder a /admin, se le pedirá que inicie sesión.

Sin embargo, hay un par de aspectos de React Router que debemos cubrir primero.

En la versión 6, la redirección programática a una nueva ubicación se logra mediante el hook useNavigate. Este hook proporciona una función que se puede utilizar para navegar de forma programática a una ruta diferente. Puede aceptar un objeto como segundo parámetro, utilizado para especificar varias opciones. Por ejemplo:

const navigate = useNavigate();navigate('/login', {  state: { from: location },  replace: true});

Esto redireccionará al usuario a /login, pasando un valor de location para almacenar en el estado del historial, al que luego podemos acceder en la ruta de destino mediante el hook useLocation. Al especificar replace: true, también reemplazará la entrada actual en el historial en lugar de agregar una nueva. Esto imita el comportamiento del componente <Redirect> en la versión 5.

Para resumir: si alguien intenta acceder a la ruta /admin sin estar conectado, será redirigido a la ruta /login. La información sobre la ubicación actual se pasa a través de la propiedad state, de modo que si la autenticación es exitosa, el usuario puede ser redirigido de vuelta a la página a la que intentaba acceder originalmente.

Rutas personalizadas

Lo siguiente que debemos analizar son las rutas personalizadas. Una ruta personalizada en React Router es un componente definido por el usuario que permite funcionalidades o comportamientos adicionales durante el proceso de enrutamiento. Puede encapsular lógica de enrutamiento específica, como comprobaciones de autenticación, y representar diferentes componentes o realizar acciones basadas en ciertas condiciones.

Crea un nuevo archivo PrivateRoute.js en el directorio src y agrega el siguiente contenido:

import { useEffect } from 'react';import { useNavigate, useLocation } from 'react-router-dom';import { fakeAuth } from './Login';const PrivateRoute = ({ children }) => {  const navigate = useNavigate();  const location = useLocation();  useEffect(() => {    if (!fakeAuth.isAuthenticated) {      navigate('/login', {        state: { from: location },        replace: true,      });    }  }, [navigate, location]);  return fakeAuth.isAuthenticated ? children : null;};export default PrivateRoute;

Aquí hay varias cosas sucediendo. En primer lugar, importamos algo llamado fakeAuth, que expone una propiedad isAuthenticated. Lo analizaremos en más detalle pronto, pero por ahora es suficiente saber que esto es lo que usaremos para determinar el estado de inicio de sesión del usuario.

El componente acepta una propiedad children. Esto será el contenido protegido que el componente <PrivateRoute> envuelve cuando se llama. Por ejemplo:

<PrivateRoute>  <Admin /> <-- children</PrivateRoute>

En el cuerpo del componente, los hooks useNavigate y useLocation se utilizan para obtener la función navigate y el objeto de location respectivamente. Si el usuario no está autenticado, verificado por !fakeAuth.isAuthenticated, se llama a la función navigate para redirigir al usuario a la ruta /login, como se describe en la sección anterior.

La declaración de retorno del componente verifica nuevamente el estado de autenticación. Si el usuario está autenticado, se representan los componentes secundarios. Si el usuario no está autenticado, se devuelve null, sin representar nada.

También ten en cuenta que estamos envolviendo la llamada a navigate en el hook useEffect de React. Esto se debe a que la función navigate no se debe llamar directamente dentro del cuerpo del componente, ya que provoca una actualización de estado durante la representación. Envolverlo dentro de un hook useEffect asegura que se llame después de que el componente se haya representado.

Aviso de seguridad importante

En una aplicación del mundo real, debes validar cualquier solicitud de un recurso protegido en tu servidor. Permíteme decirlo de nuevo…

En una aplicación del mundo real, debes validar cualquier solicitud de un recurso protegido en tu servidor.

Esto se debe a que cualquier cosa que se ejecute en el cliente puede potencialmente ser descompilada y manipulada. Por ejemplo, en el código anterior, alguien podría abrir las herramientas de desarrollo de React y cambiar el valor de isAuthenticated a true, obteniendo así acceso al área protegida.

La autenticación en una aplicación de React merece un tutorial aparte, pero una forma de implementarla sería utilizando JSON Web Tokens. Por ejemplo, podrías tener un punto final en tu servidor que acepte una combinación de nombre de usuario y contraseña. Cuando lo reciba (a través de Ajax), verifica si las credenciales son válidas. Si lo son, responde con un JWT, que la aplicación de React guarda (por ejemplo, en sessionStorage), y si no lo son, envía una respuesta de 401 Unauthorized de vuelta al cliente.

Suponiendo un inicio de sesión exitoso, el cliente luego enviaría el JWT como un encabezado junto con cualquier solicitud de un recurso protegido. Esto se validaría luego por el servidor antes de enviar una respuesta.

Cuando se almacenan contraseñas, el servidor no las almacena en texto plano. En lugar de eso, las encripta, por ejemplo, utilizando bcryptjs.

Implementando la Ruta Protegida

Ahora vamos a implementar nuestra ruta protegida. Modifica App.js así:

import { Link, Route, Routes } from 'react-router-dom';import { Categories, Desktops, Laptops } from './Categories';import Products from './Products';import Login from './Login';import PrivateRoute from './PrivateRoute';const Home = () => (  <div>    <h2>Inicio</h2>    <p>¡Bienvenido a nuestra página de inicio!</p>  </div>);const Admin = () => (  <div>    <h2>¡Bienvenido administrador!</h2>  </div>);export default function App() {  return (    <div>      <nav>        <ul>          <li>            <Link to="/">Inicio</Link>          </li>          <li>            <Link to="/categories">Categorías</Link>          </li>          <li>            <Link to="/products">Productos</Link>          </li>          <li>            <Link to="/admin">Área Admin</Link>          </li>        </ul>      </nav>      <Routes>        <Route path="/" element={<Home />} />        <Route path="/categories/" element={<Categories />}>          <Route path="desktops" element={<Desktops />} />          <Route path="laptops" element={<Laptops />} />        </Route>        <Route path="/products/*" element={<Products />} />        <Route path="/login" element={<Login />} />        <Route          path="/admin"          element={            <PrivateRoute>              <Admin />            </PrivateRoute>          }        />      </Routes>    </div>  );}

Como puedes ver, hemos añadido un componente <Admin> en la parte superior del archivo, y estamos incluyendo nuestro <PrivateRoute> dentro del componente <Routes>. Como se mencionó anteriormente, esta ruta personalizada renderiza el componente <Admin> si el usuario ha iniciado sesión. De lo contrario, el usuario es redirigido a /login.

Finalmente, crea Login.js y añade el siguiente código:

// src/Login.jsimport { useState, useEffect } from 'react';import { useNavigate, useLocation } from 'react-router-dom';export default function Login() {  const navigate = useNavigate();  const { state } = useLocation();  const from = state?.from || { pathname: '/' };  const [redirectToReferrer, setRedirectToReferrer] = useState(false);  const login = () => {    fakeAuth.authenticate(() => {      setRedirectToReferrer(true);    });  };  useEffect(() => {    if (redirectToReferrer) {      navigate(from.pathname, { replace: true });    }  }, [redirectToReferrer, navigate, from.pathname]);  return (    <div>      <p>Debes iniciar sesión para ver la página en {from.pathname}</p>      <button onClick={login}>Iniciar sesión</button>    </div>  );}/* Una función de autenticación falsa */export const fakeAuth = {  isAuthenticated: false,  authenticate(cb) {    this.isAuthenticated = true;    setTimeout(cb, 100);  },};

En el código anterior, intentamos obtener un valor para la URL a la que el usuario intentaba acceder antes de que se le pida iniciar sesión. Si esto no está presente, lo establecemos como { pathname: "/" }.

A continuación, utilizamos el hook useState de React para inicializar una propiedad redirectToReferrer en false. Dependiendo del valor de esta propiedad, el usuario es redirigido a donde iba (es decir, el usuario ha iniciado sesión) o se le presenta un botón para iniciar sesión.

Una vez que se hace clic en el botón, se ejecuta el método fakeAuth.authenticate, que establece fakeAuth.isAuthenticated en true y (en una función de devolución de llamada) actualiza el valor de redirectToReferrer a true. Esto hace que el componente se vuelva a renderizar y el usuario sea redirigido.

Demostración en funcionamiento

Unamos todas las piezas del rompecabezas, ¿de acuerdo? Aquí tienes la demostración final de la aplicación que construimos usando React router.

https://codesandbox.io/embed/react-router-demo-wpwmcv

React Router Versión 6.4

Antes de terminar, debemos mencionar el lanzamiento de React Router v6.4. A pesar de parecer una versión insignificante, esta versión introdujo nuevas características innovadoras. Por ejemplo, ahora incluye las API de carga y mutación de datos de Remix, que introducen un nuevo paradigma para mantener la interfaz de usuario sincronizada con los datos.

A partir de la versión 6.4, puedes definir una función loader para cada ruta, que se encarga de obtener los datos necesarios para esa ruta. Dentro de tu componente, utilizas el hook useLoaderData para acceder a los datos cargados por tu función loader. Cuando un usuario navega a una ruta, React Router llama automáticamente a la función de carga asociada, obtiene los datos y pasa los datos al componente a través del hook useLoaderData, sin necesidad de usar useEffect. Esto promueve un patrón donde la obtención de datos está directamente relacionada con el enrutamiento.

También hay un nuevo componente <Form> que impide que el navegador envíe la solicitud al servidor y la envía a la action de tu ruta en su lugar. React Router luego vuelve a validar automáticamente los datos en la página después de que finaliza la acción, lo que significa que todos tus hooks useLoaderData se actualizan y la interfaz de usuario se mantiene automáticamente sincronizada con tus datos.

Para utilizar estas nuevas API, necesitarás utilizar el nuevo componente <RouterProvider />. Este toma una prop router que se crea utilizando la nueva función createBrowserRouter.

Discutir todos estos cambios en detalle está fuera del alcance de este artículo, pero si estás interesado en obtener más información, te animo a seguir el tutorial oficial de React Router.

Resumen

Como has visto en este artículo, React Router es una biblioteca potente que complementa a React para construir un enrutamiento mejor y más declarativo en tus aplicaciones de React. En el momento de escribir este artículo, la versión actual de React Router es la v6.18 y la biblioteca ha experimentado cambios sustanciales desde la v5. Esto se debe en parte a la influencia de Remix, un framework web de pila completa escrito por los mismos autores.

En este tutorial, aprendimos:

  • cómo configurar e instalar React Router
  • lo básico del enrutamiento y algunos componentes esenciales como <Routes>, <Route> y <Link>
  • cómo crear un enrutador mínimo para la navegación y rutas anidadas
  • cómo construir rutas dinámicas con parámetros de ruta
  • cómo trabajar con los hooks de React Router y su nuevo patrón de renderización de rutas

Finalmente, aprendimos algunas técnicas de enrutamiento avanzadas al crear la demostración final para rutas protegidas.

Preguntas frecuentes

¿Cómo funciona el enrutamiento en React Router v6?

Esta versión introduce una nueva sintaxis de enrutamiento utilizando los componentes <Routes> y <Route>. El componente <Routes> envuelve a los componentes individuales <Route>, que especifican la ruta y el elemento a renderizar cuando la ruta coincide con la URL.

¿Cómo configuro rutas anidadas?

Las rutas anidadas se crean colocando componentes <Route> dentro de otros componentes <Route> en el código JSX. De esta manera, los componentes anidados <Route> reflejan de forma natural la estructura anidada de las URL que representan.

¿Cómo redirijo a los usuarios a otra página?

Puedes utilizar el hook useNavigate para navegar programáticamente a los usuarios a otra página. Por ejemplo, const navigate = useNavigate(); y luego navigate('/path'); para redirigir al camino deseado.

¿Cómo paso props a los componentes?

En v6, puedes pasar props a los componentes incluyéndolos en la propiedad elemento de un componente <Route>, de la siguiente manera: <Route path="/path" element={<Component prop={value} />} />.

¿Cómo accedo a los parámetros de la URL en React Router v6?

Los parámetros de la URL se pueden acceder utilizando el hook useParams. Por ejemplo, si la ruta se define como <Route path=":id" element={<Component />} />, puedes utilizar const { id } = useParams(); para acceder al parámetro id dentro de <Component />.

¿Cuáles son las novedades en React Router v6.4?

La versión 6.4 introduce muchas características nuevas inspiradas en Remix, como los generadores de datos y createBrowserRouter, con el objetivo de mejorar la obtención y el envío de datos. Puedes encontrar una lista exhaustiva de las nuevas características aquí.

¿Cómo migro de React Router v5 a v6?

La migración consiste en actualizar las configuraciones de tus rutas a la nueva sintaxis de los componentes <Routes> y <Route>, actualizar los hooks y otros métodos de la API a sus contrapartes en la v6, y abordar cualquier cambio incompatible en la lógica de enrutamiento de tu aplicación. Puedes encontrar una guía oficial aquí.

Comparte este artículo


Leave a Reply

Your email address will not be published. Required fields are marked *