React — React Router V6
Temario
- Introducción
- ¿Qué es enrutamiento?
- ¿Qué es React Router?
- Componentes primarios de React Router
- Ejemplos con React Router V6
- Los hooks de React Router (useNavigate, useLocation, useParams, matchPath)
- Enrutamiento anidado
i. Introducción
Recordemos que React es una biblioteca para crear aplicaciones de una sola página (SPA).
SPA (Single Page Application): Es una aplicación que funciona completamente en el navegador sin requerir recargas completas de página. El contenido se carga automáticamente, ofreciendo una experiencia de usuario más fluida.
React, así como otras bibliotecas o frameworks son herramientas que permiten que las aplicaciones funcionen realizando todo el trabajo desde el front.
La mayoría de los usuarios de internet están acostumbrados a navegar entre diferentes páginas, esperando que funcionen de manera predecible:
- Cada vista de la aplicación debería de tener su propia URL específica. como
www.mypage.com/about
,www.mypage.com/products
, etc. - Las páginas generadas dinámicamente deberían de ser accesibles, por ejemplo,
www.mypage.com/prodducts/shirts/abc
, dondeABC
es un identificador de un producto. - Los botones ‘adelante’ y ‘atrás’ del navegador deberían de funcionar como se espera.
- Si un usuario ingresa directamente una URL, como
www.mypage.com/prodducts/shirts/abc
, esta debería de cargar correctamente.
En React, no podemos realizar estas acciones tan fácilmente de forma nativa. Por eso se han desarrollado APIs que permiten implementar enrutamiento de páginas manteniendo todas las ventajas de una SPA. En una entrada anterior [ref], utilizamos ejemplos con la versión 5, ahora, todos los ejemplos estarán actualizados a la versión 6, donde introduce varias mejoras y cambios importantes.
ii. ¿Qué es enrutamiento?
Enrutamiento (routing) es el proceso de definir cómo una aplicación web maneja diferentes URL o rutas que el usuario puede visitar, es decir, el proceso de mantener sincronizada una URL con el contenido de una aplicación, permitiendo controlar el flujo de datos de una aplicación.
Veamos algunos ejemplos de Google Play:
Podemos observar que dependiendo la ruta es el contenido a mostrar. Después de un breve resumen, entremos de lleno a React Router.
iii. React Router
Es una herramienta de enrutamiento para construir aplicaciones web con React, ya que permite crear una navegación fluida tipo SPA, mejorando tanto la experiencia del usuario como el rendimiento general de la aplicación.
:: Breve historia
- Por allá del 2014 Michael Jackson[ref] y Ryan Florence[ref] tuvieron la genial idea de crear un enrutador para React.
- A finales del 2019 deciden fusionarse con el enrutador reach[ref], con el objetivo de tener lo mejor de ambos, donde, a principios del 2020 sale la versión 5 (teniendo soporte para hooks).
- Y desde el 2021 se encuentra la versión 6[ref].
:: ¿Qué beneficios tiene usar React Router?
- Con React Router, la navegación se vuelve fluida y sin interrupciones, es decir, la navegación se maneja del lado del cliente.
- Permite navegar sin recargar toda la página, actualizando solo la parte necesaria del contenido, haciendo la navegación más rápida.
- Permite definir rutas específicas dentro de la aplicación como
/home, /about
, etc. También define rutas dinámicas que pueden contener parámetros variables como/products/:id
donde elid
es un identificador. - Accede a los parámetros de ruta, lo cual es bastante útil al momento de tener filtros, búsquedas o detalles de productos.
- Protege ciertas rutas con la finalidad de que solo los usuarios autenticados puedan acceder a ellas, permitiendo un mayor control sobre qué se muestra en cada ruta.
- Se puede crear rutas anidadas o subrutas, permitiendo una estructura más compleja y jerárquica.
- Soporta redirecciones automáticas y las rutas no encontradas (404).
- Es compatible con SEO (motores de búsqueda).
:: ¿Cómo funciona?
Cuando el usuario hace clic en un enlace o escribe directamente la URL en la barra de direcciones, React Router actualiza la interfaz sin recargar toda la página, esto se logra manipulando el historial del navegador y utilizando componentes de React para cambiar el contenido dinámicamente, todo del lado del cliente.
Visualmente, debería funcionar de la siguiente manera:
Si analizamos la página de Google Play Store [ref], a primera vista podemos observar que quedaría de la siguiente manera:
Otro ejemplo:
iv. Componentes primarios de React Router
React router se puede dividir en tres categorías principales: Enrutadores (Routers), Comparadores de ruta (Route Matchers) y Navegadores (Navigators). Vamos a explicar cada uno de los conceptos:
:: Enrutadores
Son los encargados de gestionar la navegación de la aplicación. Sirven como contenedores principales que determinan el cómo y el cuándo se muestran diferentes rutas. Ejemplos:
<BrowserRouter> o createBrowserRouter[ref]
: Usa rutasURL
normales (*.com/about
), se requiere que el servidor esté configurado correctamente (en el caso decreate-react-app
ya viene predeterminado)[ref]. Utiliza la API del historial del navegador para mantener la interfaz de usuario sincronizada con la URL.<HashRouter> o createHashRouter[ref]
: Almacena la ubicación en la parte de laURL
con un hash#
(*.com/#/about
), de esta forma no se necesita ninguna configuración especial del lado del servidor. No es recomendable utilizar la URL en formato hash.
:: Comparadores de ruta
<Switch> o <Routes>[ref]
: Este componente se va a encargar de buscar a través de sus hijos<Route>
laURL
que sea igual o parecida para mostrar su contenido, en caso de que encuentre ignorará los demás; en caso de que no encuentre nada devolveránull
.<Route>
: Define una ruta específica y el componente que se debe de renderizar cuando esa ruta coincide con la URL actual.<Outlet>
: Componente donde se renderizan los componentes de rutas definidas como hijos (children
) en la configuración del enrutador decreateBrowserRouter
.
Nota: Se recomienda poner primero las rutas más específicas a las menos específicas, ya que se corre el riesgo que haga mal el enrutamiento.
:: Navegadores
<Link>
: Componente para crear enlaces dentro de la aplicación. Reemplaza las etiquetas<a>
de HTML para crear enlaces.<NavLink>
: Es un componente especial, que nos sirve para poder cambiar el estilo del enlace (siempre y cuando coincida).<Navigate>[ref] o redirect[ref] o useNavigate[ref]
: Cuando se quiere forzar la navegación a unaURL
específica.
Ya teniendo todos los conceptos básicos, vamos a los ejemplos…
v. Ejemplos con React Router V6
Vamos a crear un proyecto con vite
[ref]:
npm create vite@latest vite-react-router-examples-v6 -- --template react
:: Instalación
Vamos a la carpeta e instalamos React router:
npm i --save react-router-dom
:: Página básica
Vamos a hacer un ejemplo con React Router v6 utilizando componentes que son muy similares a los de la versión 5.
Primero, abre el archivo src/App.jsx
y realiza los siguientes cambios para que quede de la siguiente manera:
function App() {
return (
<div className="App">
</div>
);
}
export default App;
A continuación, importamos la librería react-router-dom
(recuerda instalarla en la consola con npm i react-router-dom
) y agregaremos los componentes BrowserRouter
, Routes
, Route
y Link
.
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';
Donde:
BrowserRouter
aliasRouter
: Componente principal encargado del enrutamiento de la aplicación.Routes
: Componente que actúa como contenedor para los componentes deRoute
que definimos en la aplicación. Se asegura que solo renderice el componente asociado (este componente reemplaza alSwitch
de la versión 5).Route
: Representa cada ruta específica en la aplicación.Link
: Componente para crear enlaces dentro de la aplicación, permitiendo cambiar de ruta sin recargar toda la página, aprovechando el enrutamiento del lado del cliente por React Router.
Complementando la explicación anterior, veamos un ejemplo sencillo:
Primero, colocaremos el componente encargado del enrutamiento:
<Router>
</Router>
Luego, agregaremos el menú en la parte estática, así como el componente que actuará como contenedor para las rutas:
<Router>
<nav>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/about">About</Link></li>
<li><Link to="/products">Products</Link></li>
</ul>
</nav>
{/* Contenedor para definir las diferentes rutas de la aplicación.*/}
<Routes>
</Routes>
</Router>
Finalmente, añadimos cada una de las rutas específicas junto con su contenido:
<Router>
<nav>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/about">About</Link></li>
<li><Link to="/products">Products</Link></li>
</ul>
</nav>
{/* Contenedor para definir las diferentes rutas de la aplicación.*/}
<Routes>
{/* Componente que define qué componente se renderiza para cada ruta.*/}
<Route path="/" element={<h1>I'm main page</h1>} />
<Route path="/about" element={<h1>I'm main about</h1>} />
<Route path="/products" element={<h1>I'm main products</h1>} />
</Routes>
</Router>
El código completo:
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';
function App() {
return (
<div className="App">
{/* Contenedor principal encargado de manejar el enrutamiento.*/}
<Router>
{/* Contenido estático.*/}
<nav>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/about">About</Link></li>
<li><Link to="/products">Products</Link></li>
</ul>
</nav>
{/* Contenedor para definir las diferentes rutas de la aplicación.*/}
<Routes>
{/* Componente que define qué componente se renderiza para cada ruta.*/}
<Route path="/" element={<h1>I'm main page</h1>} />
<Route path="/about" element={<h1>I'm main about</h1>} />
<Route path="/products" element={<h1>I'm main products</h1>} />
</Routes>
</Router>
</div>
);
}
export default App;
Levantamos el servidor (npm run dev
) y si has seguido los pasos correctamente, deberías ver lo siguiente:
Actualizaremos el código utilizando componentes de React. Para ello, crearemos una carpeta llamada src/Components
y dentro de ella, añadiremos los siguientes componentes:
src/Components/Main.jsx
import React from 'react';
const Main = () => (
<h1>I'm Main page</h1>
);
export default Main;
src/Components/About.jsx
import React from 'react';
const About = () => (
<h1>I'm About page</h1>
);
export default About;
src/Components/Products.jsx
import React from 'react';
const Products = () => (
<h1>I'm Products page</h1>
);
export default Products;
src/Components/Nav.jsx
import React from 'react';
import { Link } from 'react-router-dom';
const Nav = () => (
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/products">Products</Link>
</li>
</ul>
</nav>
);
export default Nav;
Importamos los componentes en src/App.jsx
y los sustituimos en las partes correspondientes:
import { BrowserRouter as Router, Routes, Route} from 'react-router-dom';
import Nav from './Components/Nav';
import Main from './Components/Main';
import About from './Components/About';
import Products from './Components/Products';
function App() {
return (
<div className="App">
<Router>
<Nav />
<Routes>
<Route path="/" element=<Main/> />
<Route path="/about" element=<About/> />
<Route path="/products" element=<Products/> />
</Routes>
</Router>
</div>
);
}
export default App;
Si has hecho bien los cambios, no deberás de notar ningún cambio visual en el ejemplo.
— Ejemplo funcionando en Sandbox:
— Descarga el código
Github[tag: 0.1.0][ref].
:: Página básica con la v6
React Router v6 nos ofrece un enfoque más declarativo y flexible para definir rutas, ya que proporciona características avanzadas como la carga de datos y el manejo de errores directamente en la configuración de las rutas.
Vamos a actualizar nuestro código utilizando los nuevos componentes createBrowserRouter
y RouterProvider
. Para ello, en el archivo src/App.js
, actualizaremos la importación de la librería react-router-dom
para incluir los siguientes componentes: createBrowserRouter
, RouterProvider
,Link
y Outlet
.
import { createBrowserRouter, RouterProvider, Link, Outlet } from 'react-router-dom';
Donde:
createBrowserRouter
: Función encargada de crear el enrutamiento de la aplicación. Recibe un arreglo de objetos donde cada objeto representa una ruta específica.RouterProvider
: Componente que envuelve toda la aplicación o parte que necesita el enrutamiento. Toma la configuración decreateBrowserRouter
y gestiona la navegación.Link
: Componente para crear enlaces dentro de la aplicación, permitiendo cambiar de ruta sin recargar toda la página, aprovechando el enrutamiento del lado del cliente por React Router.Outlet
: Renderiza el componente de la ruta actual
Vamos a migrar poco a poco el ejemplo a la nueva forma. El primer paso será importar createBrowserRouter
y crear una constante llamada router
, en la que invocamos createBrowserRouter
para definir las rutas de nuestra aplicación:
import { createBrowserRouter } from 'react-router-dom';
const router = createBrowserRouter([
{
path: '/', //Ruta de la URL
element: (), // Componente que debe renderizarse
children: [], //Componentes que son navegables
},
]);
En la función App
añadimos el componente que actuará como contenedor para las rutas:
import { createBrowserRouter } from 'react-router-dom';
const router = createBrowserRouter([
{
path: '/', //Ruta de la URL
element: (), // Componente que debe renderizarse
children: [], //Componentes que son navegables
},
]);
function App() {
return (
<RouterProvider router={router} />
);
}
export default App;
Luego, agregaremos el menú en la parte estática, así como el componente encargado de renderizar las rutas definidas como hijos.
import { createBrowserRouter, Outlet, RouterProvider } from 'react-router-dom';
//Importamos el componente Nav
import Nav from './Components/Nav';
const router = createBrowserRouter([{
path: '/',
// Componente principal que debe renderizarse
element: (
<div className="App">
<Nav /> {/* Componente del menú */}
<Outlet /> {/* Componente donde se renderizan los hijos */}
</div>
),
children: [],
}]);
function App() {
return (
<RouterProvider router={router} />
);
}
export default App;
Finalmente, agregamos cada una de las rutas hijas específicas junto con su contenido correspondiente:
import { createBrowserRouter, Outlet, RouterProvider } from 'react-router-dom';
import Nav from './Components/Nav';
import Main from './Components/Main';
import About from './Components/About';
import Products from './Components/Products';
const router = createBrowserRouter([
{
path: '/',
element: (
<div className="App">
<Nav />
<Outlet />
</div>
),
children: [
{
path: '/',
element: <Main />,
},
{
path: '/about',
element: <About />,
},
{
path: '/products',
element: <Products />,
},
],
},
]);
function App() {
return <RouterProvider router={router} />;
}
export default App;
Levantamos el servidor y si has seguido los pasos correctamente, deberías ver lo siguiente:
— Ejemplo funcionando en Sandbox:
— Descarga el código
Github[tag: 0.1.1][ref].
:: Ejemplo con dos partes estáticas
Continuando con el ejemplo, vamos a incluir un footer y, además, añadiremos más contenido.
A continuación, vamos a actualizar los siguientes componentes:
En src/Components/Products.jsx
, agregamos algunas imágenes random:
import React from 'react';
const Products = () => (
<div>
<h1>Hello, I'm Products component</h1>
<div>
<img src="https://picsum.photos/id/100/200/200" />
<img src="https://picsum.photos/id/200/200/200" />
<img src="https://picsum.photos/id/300/200/200" />
<img src="https://picsum.photos/id/400/200/200" />
<img src="https://picsum.photos/id/500/200/200" />
<img src="https://picsum.photos/id/600/200/200" />
</div>
</div>
);
export default Products;
En src/Components/About.jsx
, agregamos un poco de texto:
import React from 'react';
const About = () => (
<div>
<h1>Hello, I'm About component</h1>
<p>
Bacon ipsum dolor amet chuck meatloaf doner shankle picanha. Ham hock pork
belly capicola buffalo ground round tail. Turkey biltong spare ribs,
alcatra short loin andouille swine meatloaf ham hock drumstick kevin
frankfurter salami. Andouille tail pig brisket beef ribs.
</p>
<p>
Ribeye fatback turkey pig. Chuck fatback ham, meatball hamburger alcatra
doner. Filet mignon ham hock ham, salami ribeye pork spare ribs pig.
Prosciutto meatball pig pork loin rump spare ribs, burgdoggen cupim flank
alcatra.
</p>
<p>
Tri-tip pastrami pork, ribeye corned beef swine pork loin. Shank pork loin
boudin short ribs pork belly venison meatball shoulder swine ham hock
chicken. Fatback landjaeger t-bone tri-tip flank pork pork loin leberkas.
Fatback pork spare ribs brisket beef leberkas. Shoulder swine leberkas
bacon bresaola filet mignon strip steak pork belly kielbasa t-bone.
Fatback hamburger burgdoggen jerky landjaeger beef rump frankfurter flank
prosciutto pork belly andouille chislic jowl.
</p>
</div>
);
export default About;
Posteriormente, dentro de la carpeta src/Components
añadiremos los siguientes componentes:
src/Components/Footer.jsx
, que es el pie de la página.
import React from 'react';
const Footer = () => (
<footer>
<p>© 2024 Hello I'm a little footer.</p>
</footer>
);
export default Footer;
src/Components/AppLayout.jsx
, añadimos el menú de navegación, el componente donde se van a renderizar los elementos hijos (Outlet
) y el footer:
import React from 'react';
import { Outlet, } from 'react-router-dom';
import Nav from './Nav';
import Footer from './Footer';
const AppLayout = () => (
<div className="App">
<Nav />
<Outlet />
<Footer />
</div>
);
export default AppLayout;
Para mantener el elemento principal más limpio y organizado, lo hemos convertido en un componente llamado AppLayout
. De esta manera, si en algún momento necesitamos agregar más elementos, el código no se volverá complicado.
Vamos a src/App.jsx
para configurar el enrutamiento e importar los componentes que hemos creado:
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import Main from './Components/Main';
import About from './Components/About';
import Products from './Components/Products';
import AppLayout from './Components/AppLayout';
const router = createBrowserRouter([
{
path: '/',
element: <AppLayout />,
children: [
{
path: '/',
element: <Main />,
},
{
path: '/about',
element: <About />,
},
{
path: '/products',
element: <Products />,
},
],
},
]);
function App() {
return <RouterProvider router={router} />;
}
export default App;
Levantamos el servidor y si has seguido los pasos correctamente, deberías ver lo siguiente:
En la animación de arriba podemos observar que solo renderiza una parte y no toda la página.
— Ejemplo funcionando en Sandbox:
— Descarga el código
Github[tag: 0.1.2][ref].
vi. Los hooks de React Router
React Router incluye varios hooks personalizados que nos permiten acceder al estado del enrutador y manejar la navegación de manera más eficiente. Los hooks más populares son:
:: useNavigate
En la versión 5 de React Router, se utilizaba useHistory
para manejar la navegación. En la versión 6, useHistory
ha sido reemplazado por useNavigate
. Este cambio se implementó para simplificar y mejorar la API de navegación, haciendo que su uso sea más intuitivo y directo.
Se importa de la siguiente manera:
import { useNavigate } from "react-router-dom";
Se utiliza de la siguiente forma:
let navigate = useNavigate(link, options);
Donde:
link
: Te permite redirigir a una ruta específica, navegar hacia atrás o hacia adelante. Ejemplos:
navigate('/about'); //Ir a una ruta específica
navigate(-1); // Ir hacia atrás en el historial (más negativo el número más atras de la navegación)
navigate(1); // Ir hacia adelante en el historial (más positivo el número más adelante de la navegación)
options
: Contiene más propiedades como:
— replace
: Si se establece como true
, la nueva entrada reemplazará a la entrada actual en el historial de navegación, en lugar de agregar una nueva entrada. Es útil cuando se quiere que el usuario no pueda volver a la página anterior utilizando el botón de atrás del navegador.
navigate('/about', { replace: true });
— state
: Permite pasar un objetivo a la nueva ruta. Este estado es accesible desde el componente al que se navega mediante el hook useLocation
. Es útil para pasar datos temporales sin exponerlos en la URL.
navigate('/about', { state: { myKey: 'myValue'} });
— preventScrollReset
: Si se establece como true
, evita que la página se desplace automáticamente al principio después de navegar. Es útil cuando se quiere mantener la posición de desplazamiento después de una acción de navegación.
navigate('/about', { preventScrollReset: true });
— relative
: Se indica si la ruta especificada debe considerarse relativa a la ruta actual en el lugar de ser absoluta (path
— la ruta proporcionada es relativa a la URL actual, route
— la ruta es relativa a la ruta padre en la configuración del enrutador, es decir, se considera como una ruta absoluta).
Supongamos que estas en la ruta example.com/products
:
// Navegar a una subruta 'details' relativa a la URL actual '/products'
// example.com/products/details
navigate('details', { relative: 'path' });
// Navegar a la ruta 'details' relativa a la ruta padre en el enrutador
// example.com/details
navigate('details', { relative: 'route' });
Podemos observar que, si usamos relative: 'path'
, la ruta será relativa a la URL actual, en este caso /products
. Por otro lado, si usamos relative: 'route'
, la ruta será relativa a la ruta padre definida en el enrutador.
— Es útil :
- Proporciona una forma programática de controlar la navegación.
- Navegar programáticamente sin recargar la página completa ofrece una experiencia de usuario más rápida y fluida. Esto permite realizar redirecciones basadas en condiciones específicas, según los datos ingresados.
- Mejor manejo del historial de navegación del navegador.
- Permite pasar datos de estado adicionales a la nueva ruta sin exponerlos en la URL.
- El uso de
relative
facilita el manejo de rutas anidadas y permite navegar de manera relativa a la ruta actual, haciendo que las rutas complejas sean más intuitivas y fáciles de mantener.
— Ejemplo
Crearemos tres botones: uno para ir a la página /about
, otro para retroceder en la navegación, y uno más para avanzar.
Abre el archivo src/Components/Main.jsx
para probar el uso de useNavigate
.
import React from 'react';
import { useNavigate } from 'react-router-dom';
const Main = () => {
const navigate = useNavigate();
const handleAbout = () => navigate('/about'); //add programmatic routing
const handleBack = () => navigate(-1); //Back programmatic routing
const handleForward = () => navigate(1); //Forward programmatic routing
return (
<div>
<h1>Hello, I'm Main component </h1>
<hr />
<h2>useNavigate</h2>
<button onClick={handleAbout}>Go to "About"</button>
<button onClick={handleBack}>Back</button>
<button onClick={handleForward}>Forward</button>
<hr />
</div>
);
};
export default Main;
Levantamos el servidor y si has seguido los pasos correctamente, deberías ver lo siguiente:
— Ejemplo funcionando en Sandbox:
— Descarga el código
Github[tag: 0.2.0][ref].
:: useLocation
Este hook devuelve un objeto mutable que proporciona información sobre la ubicación actual de la aplicación.
Se importa de la siguiente manera:
import { useLocation } from 'react-router-dom';
Se utiliza de la siguiente forma:
let location = useLocation();
El objeto que retorna es el siguiente:
pathname
: Contiene la ruta de la URL actual (sin el dominio).search
: Contiene la cadena de consulta de la URL, incluyendo el símbolo?
.hash
: Contiene la porción hash de la URL, que es todo lo que sigue al símbolo#
.state
: Contiene el estado opcional que se ha pasado durante la navegación mediante el hookuseNavigate
.
— Es útil:
- Accede a detalles de la URL actual (ruta, parámetros, estado de la navegación, entre otros).
- Es similar a
window.location
, pero representa el estado y la ubicación del enrutador en una aplicación React. - Útil cuando se necesita pasar información de un componente a otro sin exponerlo en la URL.
- Es reactivo a los cambios de ruta; es decir, el hook se actualiza automáticamente cuando la ruta cambia, lo que permite que los componentes se vuelvan a renderizar en función de la nueva ubicación.
- Para poder hacer el breadcrumb[ref].
— Ejemplo
Vamos a crear un botón que redirige a la página /products
, enviando un parámetro de consulta (query param
) llamado print
con un valor de 20. Este parámetro se utilizará en la página de destino para controlar la cantidad de imágenes o elementos que se mostrarán. Si el parámetro no viene indicado, se mostrará un valor predeterminado (5).
Abre el archivo src/Components/Main.jsx
para implementarlo.
import React from 'react';
import { useNavigate } from 'react-router-dom';
const Main = () => {
const navigate = useNavigate();
const handleAbout = () => navigate('/about');
const handleBack = () => navigate(-1);
const handleForward = () => navigate(1);
//Agregamos la acción del nuevo botón para mandar parametros por la URL
const handleProductsWithUrlParams = () => navigate('/products?print=20');
return (
<div>
<h1>Hello, I'm Main component </h1>
<hr />
<h2>useNavigate</h2>
<button onClick={handleAbout}>Go to "About"</button>
<button onClick={handleBack}>Back</button>
<button onClick={handleForward}>Forward</button>
{/* Agregamos el botón*/}
<button onClick={handleProductsWithUrlParams}>Goes to "Products" with the query params "print=20".</button>
<hr />
</div>
);
};
export default Main;
Abre el archivo src/Components/Products.jsx
, donde utilizaremos el hook useLocation
para revisar sus propiedades, en particular, la propiedad search
. Crearemos una instancia de URLSearchParams
para obtener los parámetros de búsqueda que se encuentran después del símbolo ?
.
import React from 'react';
import { useLocation } from 'react-router-dom';
const Products = () => {
const location = useLocation();
const queryParams = new URLSearchParams(location.search);
//Si no encuentra print por default serán 5
const print = queryParams.get('print') || 5;
return (
<div>
<h1>Hello, I'm Products component</h1>
<h3>Images to display:{print} </h3>
<div>
{
(new Array(Number(print)).fill()).map((v, i) =>(
<img src={'https://picsum.photos/id/1'+i+'/200/200'} alt={'img'+i} key={i}/>
))
}
</div>
</div>
);
};
export default Products;
Levantamos el servidor y si has seguido los pasos correctamente, deberías ver lo siguiente:
Podemos observar que, si no se envía el parámetro de consulta print
, por defecto se mostrarán 5 imágenes. En caso contrario, si se envía el valor de 20 a través del botón, se generarán 20 imágenes. Es útil para manejar filtros, paginaciones u otros datos dinámicos sin modificar el estado global de la aplicación.
— Ejemplo
Vamos a crear un botón que redirige a la página /about
, enviando un estado (state
) llamado name
. Este estado se podrá utilizar en la página de destino para personalizar el contenido de acuerdo con el valor recibido. Si el estado no está presente, se mostrará por defecto "Stranger", y en caso de que se envíe, se utilizará el valor proporcionado en el estado.
Abre el archivo src/Components/Main.jsx
para implementarlo.
import React from 'react';
import { useNavigate } from 'react-router-dom';
const Main = () => {
const navigate = useNavigate();
const handleAbout = () => navigate('/about');
const handleBack = () => navigate(-1);
const handleForward = () => navigate(1);
const handleProductsWithUrlParams = () => navigate('/products?print=20');
//Agregamos la acción del nuevo botón para mandar parametros por la URL
const handleAboutWithState = () => navigate('/about', { state: { name: 'Mauricio' } });
return (
<div>
<h1>Hello, I'm Main component </h1>
<hr />
<h2>useNavigate</h2>
<button onClick={handleAbout}>Go to "About"</button>
<button onClick={handleBack}>Back</button>
<button onClick={handleForward}>Forward</button>
<button onClick={handleProductsWithUrlParams}>Goes to "Products" with the query params "print=20".</button>
{/* Agregamos el botón*/}
<button onClick={handleAboutWithState}>Go to "About" with a state called name</button>
<hr />
</div>
);
};
export default Main;
Abre el archivo src/Components/About.jsx
, donde utilizaremos el hook useLocation
para revisar sus propiedades, en particular, la propiedad state
. Usaremos el estado "name"
para mostrarlo en la página.
import React from 'react';
import { useLocation } from 'react-router-dom';
const About = () => {
const location = useLocation();
//Si no tiene el estado "name", por default toma Stranger
const name = location.state?.name || 'Stranger';
return (
<div>
<h1>Hello {name}, you are in the About component.</h1>
<p>
Bacon ipsum dolor amet chuck meatloaf doner shankle picanha. Ham hock
pork belly capicola buffalo ground round tail. Turkey biltong spare
ribs, alcatra short loin andouille swine meatloaf ham hock drumstick
kevin frankfurter salami. Andouille tail pig brisket beef ribs.
</p>
...
</div>
);
};
export default About;
Levantamos el servidor y si has seguido los pasos correctamente, deberías ver lo siguiente:
Es útil ya que se enfoca en el uso de useLocation
para acceder al estado (state
) de la navegación, lo que facilita la transferencia de información entre rutas sin exponerla públicamente en la URL.
— Ejemplo funcionando en Sandbox:
— Descarga el código
Github[tag: 0.3.0][ref].
:: useParams
Este hook se utiliza para acceder a los parámetros de ruta dinámica que están definidos en las rutas. Los parámetros de ruta permiten que una parte de la URL sea dinámica, de modo que puedas capturar valores específicos (como IDs, nombres, o cualquier otra variable) directamente desde la URL y utilizarlos en tu componente.
Esta propiedad dinámica, representada con dos puntos (:
) seguida de un nombre, permitirá capturar valores variables como un identificador, que luego podrá ser utilizado en el componente correspondiente para mostrar información específica.
Se importa de la siguiente manera:
import { useParams } from 'react-router-dom';
Se utiliza de la siguiente forma:
const params= useParams();
— Es útil para:
- Te permite acceder a los parámetros dinámicos de la URL en una ruta.
- Es útil para obtener datos directamente de la URL y utilizarlos en tu componente de manera dinámica, como IDs, nombres, o cualquier otra información que se mande.
- Es muy común utilizarlo cuando queremos mostrar algún producto, usuarios, una entrada de blog, etc.
- Mejorar la experiencia del usuario como el SEO de la página.
- Enfoque es más limpio e intuitivo ya que permite que las URLs sean más descriptivas y fáciles de entender.
— Ejemplo
Vamos a actualizar el ejemplo de la galería. Actualmente, los parámetros se envían mediante query params en la URL (?print=20
), pero en esta versión utilizaremos rutas dinámicas (/products/20
)para mejorar la claridad y estructura de la URL. Para ello vamos a utilizar el hook useParams
.
En el componente src/App.jsx
, definimos la ruta para /products
indicando que incluirá una propiedad dinámica dentro de la URL.
//..
const router = createBrowserRouter([
{
path: '/',
element: <AppLayout />,
children: [
{
path: '/',
element: <Main />,
},
{
path: '/about',
element: <About />,
},
{
path: '/products/:print', // Ruta dinámica para los productos
element: <Products />,
},
],
},
]);
//...
Abre el archivo src/Components/Main.jsx
para actualizar la redirección hacia products.
import React from 'react';
import { useNavigate } from 'react-router-dom';
const Main = () => {
const navigate = useNavigate();
//...
//Actualizamos la url de query params a parametro dentro de la URL
const handleProductsWithUrlParams = () => navigate('/products/20');
return (
<div>
...
{/* Actualizamos el botón*/}
<button onClick={handleProductsWithUrlParams}>Goes to “Products” with URL “products/20”.</button>
<hr />
</div>
);
};
export default Main;
En el componente Components/Nav.jsx
actualizamos la ruta:
//...
const Nav = () => (
<nav>
<ul>
...
<Link to="/products/5">Products</Link>
</li>
</ul>
</nav>
);
//...
En el componente Components/Products.jsx
, utilizamos el hook useParams
para leer el parámetro dinámico de la URL. El objetivo es determinar cuántas imágenes se deben renderizar en función de ese valor. De esta manera, el número de imágenes mostradas en la página será dinámico, basado en la información proporcionada en la ruta, por ejemplo, /products/10
para mostrar 10 imágenes.
//...
import { useParams } from 'react-router-dom';
const Products = () => {
const { print } = useParams(); //Obtenemos la propiedad "print"
return (
<div>
<h1>Hello, I'm Products component</h1>
<h3>Images to display:{print} </h3>
<div>
{
(new Array(Number(print)).fill()).map((v, i) =>(
<img src={'https://picsum.photos/id/1'+i+'/200/200'} alt={'img'+i} key={i}/>
))
}
</div>
</div>
);
};
//...
Levantamos el servidor y si has seguido los pasos correctamente, deberías ver lo siguiente:
Es útil para acceder a los parámetros dinámicos definidos en las rutas de una aplicación. Los parámetros de ruta permiten que una parte de la URL sea variable, lo que significa que puedes capturar valores específicos directamente desde la URL.
— Ejemplo funcionando en Sandbox:
— Descarga el código
Github[tag: 0.4.0][ref]
:: matchPath
En las versión 5, el uso del useRouteMatch
se usaba para comparar la URL actual con una ruta específica y además para acceder a información sobre esa coincidencia. En la versión 6, este uso ha sido “simplificado”.
Para obtener los valores dinámicos de la URL, utilizaremos el hook useParams
:
import { useParams } from 'react-router-dom';
const Products = () => {
const { print } = useParams(); // Accede al id (print) desde la URL
return <div>Producto {print}</div>;
};
//...
Para obtener la URL actual , utilizaremos el hook useLocation
:
import { useLocation } from 'react-router-dom';
const Products = () => {
const location = useLocation();
//Contiene la ruta de la URL actual (sin el dominio)
return <div>Ruta actual: {location.pathname}</div>;
};
//...
Recuerda que el
useLocation
también te permite obtenersearch
,hash
ystate
.
— Ejemplo
Hagamos los ajustes en el componente Components/Products.jsx
:
import React from 'react';
import { useParams, useLocation } from 'react-router-dom';
const Products = () => {
const { print } = useParams();
const { pathname } = useLocation();
return (
<div>
<h1>Hello, I'm Products component</h1>
<h3>Images to display:{print} </h3>
<h4>Current URL: {pathname}</h4>
...
</div>
);
};
export default Products;
Levantamos el servidor y si has seguido los pasos correctamente, deberías ver lo siguiente:
matchPatch
es una función que compara una URL con un patrón de ruta. Te permite realizar coincidencias de rutas cuando no estás dentro del contexto de un componente, o si deseas lógica personalizada en función de las coincidencias de rutas.
Se importa de la siguiente manera:
import { matchPath } from 'react-router-dom';
Se utiliza de la siguiente forma:
const match = matchPath({ path: "/staticParam/:dynamicParam" }, "/my/path");
Acepta las siguientes propiedades:
caseSensitive
: Si la coincidencia de la ruta debe ser sensible a minúsculas y mayúsculas (por defecto NO es sensible).
const match = matchPath(
{ path: "/products/:id", caseSensitive: true },
"/Products/123"
);
// null, ya que "Products" no coincide con "products" (sensible a mayúsculas)
console.log(match);
end
: Si la coincidencia de la ruta debe coincidir exactamente con el final de la URL. Si se establece entrue
, la coincidencia solo será válida si la URL termina exactamente igual a la ruta proporcionada. Si se establece enfalse
, la coincidencia será más flexible y permitirá coincidencias parciales (por defecto es coincidencia exacta).
const match = matchPath(
{ path: "/products", end: false }, // Coincidencia parcial permitida
"/products/123"
);
// Coincide, ya que "/products" es el inicio de "/products/123"
console.log(match);
//-----------------------------------------
const match2 = matchPath(
{ path: "/products"}, // Coincidencia exacta
"/products/123"
);
// No Coincide, ya que "/products" no es el final de "/products/123"
console.log(match2);
— Es útil
- Cuando deseas hacer lógica en función de la URL sin cambiar la navegación.
- Puedes realizar lógica condicional en función de si la URL coincide con un patrón específico.
- Si necesitas verificar coincidencias de rutas sin renderizar un componente basado en la coincidencia.
— Ejemplo
Vamos a actualizar el componente src/Components/About.jsx
, donde reemplazamos el uso de state
por rutas dinámicas. La idea es que, si no se recibe ningún parámetro, se muestre el valor por defecto "Stranger". Si se recibe un parámetro, este será el que se mostrará.
Para lograr esto, utilizaremos los hooks useLocation
, useParams
, y la función matchPath
para verificar y procesar los valores dinámicos en la URL.
Lo primero que debemos hacer es modificar el archivo App.jsx
, donde agregaremos las dos posibles rutas: una para cuando se reciba un valor dinámico y otra para cuando no se reciba ningún valor.
const router = createBrowserRouter([
{
path: '/',
element: <AppLayout />,
children: [
{
path: '/',
element: <Main />,
},
{
path: '/about', //Ruta sin parámetro
element: <About />,
},
{
path: '/about/:name', //Ruta con parámetro
element: <About />,
},
{
path: '/products/:print',
element: <Products />,
},
],
},
]);
Hacemos la actualización en el componente src/Components/Main.jsx
en la acción del botón:
//...
const handleAbout = () => navigate('/about');
const handleBack = () => navigate(-1);
const handleForward = () => navigate(1);
const handleProductsWithUrlParams = () => navigate('/products/20');
//Cambiamos de mandarlo por el state a que sea por la URL
const handleAboutWithState = () => navigate('/about/Mauricio');
return (
//...
Dentro del componente src/Components/About.jsx
implementamos los cambios necesarios para manejar las rutas dinámicas. En este paso, verificamos si se ha recibido un parámetro y mostramos el valor adecuado, o bien el valor por defecto "Stranger" en caso de que no se haya proporcionado:
import React from 'react';
import { useParams, useLocation, matchPath } from 'react-router-dom';
const About = () => {
const { name } = useParams(); // Obtener el parámetro dinámico de la URL
const { pathname } = useLocation(); // Obtener la ubicación actual
// Verificamos si la URL coincide con el patrón esperado
const match = matchPath('/about/:name', pathname);
// Si no hay coincidencia o el parámetro no está presente, mostramos "Stranger"
const displayName = match && name ? name : 'Stranger';
return (
<div>
<h1>Hello {displayName}, you are in the About component.</h1>
...
</div>
);
};
export default About;
Levantamos el servidor y si has seguido los pasos correctamente, deberías ver lo siguiente:
— Ejemplo funcionando en Sandbox:
Descarga el código
Github[tag: 0.5.0][ref]
vii. Enrutamiento anidado
Vamos a generar un nuevo proyecto, vamos a simular una tienda de ropa donde tendremos secciones para hombres y mujeres. Cada sección mostrará el componente llamado Clothes
, el cual tendrá un título personalizado basado en el tipo de ropa seleccionado. Para lograr esto, utilizaremos las rutas dinámicas.
- Tener un enlace para hombres (
/mens
) y otro para mujeres (/ladies
). - Ambos enlaces cargarán el componente
Clothes
, el cual mostrará un título basado en el tipo de ropa seleccionado.
Primero, generamos el componente /src/Components/Nav.jsx
que contendrá el menú de navegación con los enlaces para las categorías de ropa para hombres y mujeres.
import React from 'react';
import { Link } from 'react-router-dom';
const Nav = () => (
<nav>
<ul>
<li>
<Link to="/mens">Men's</Link>
</li>
<li>
<Link to="/ladies">Ladies</Link>
</li>
</ul>
</nav>
);
export default Nav;
Para darle un formato más completo a la página, añadimos un footer sencillo (/src/Components/Footer.jsx
).
import React from 'react';
const Footer = () => (
<footer>
<p>© 2024 Hello I'm a little footer.</p>
</footer>
);
export default Footer;
En este paso, creamos el componente /src/Components/AppLayout.jsx
, que va a servir como estructura principal de la aplicación. Este layout incluirá el menú de navegación en la parte superior y el footer en la parte inferior. En el centro, utilizaremos el componente Outlet
para renderizar las rutas dinámicas.
import React from 'react';
import { Outlet } from 'react-router-dom';
import Nav from './Nav';
import Footer from './Footer';
const AppLayout = () => (
<div className="App">
<Nav />
<Outlet />
<Footer />
</div>
);
export default AppLayout;
Creamos el componente /src/Components/Clothes.jsx
, donde mostrará un título basado en el tipo de ropa que se ha seleccionado (hombres o mujeres). Para determinar qué tipo se está mostrando, usamos el hook useParams
para capturar el valor dinámico de la URL.
import React from 'react';
import { useParams } from 'react-router-dom';
const Clothes = () => {
const { type } = useParams();
const title = type === 'mens' ? "Men's Clothing" : "Ladies' Clothing";
return (
<div>
<h1>{title}</h1>
</div>
);
};
export default Clothes;
Levantamos el servidor y si has seguido los pasos correctamente, deberías ver lo siguiente:
— Ejemplo funcionando en Sandbox:
— Descarga el código
Github[tag: 1.0.0][ref]
No olvidemos que las rutas en React Router son componentes de React, por lo que podemos representarlas en cualquier parte de la aplicación, no solo en el componente principal App
. Esto incluye elementos secundarios, como las subrutas. El enrutamiento anidado nos permite crear subrutas dentro de otras rutas principales, lo que facilita la creación de secciones más complejas.
Siguiendo con el ejemplo anterior de la tienda de ropa, la idea es la siguiente:
- Debajo del título, se mostrará una lista de categorías (por ejemplo, “Outerwear” y “T-shirts”).
- Al hacer clic en una de esas categorías, se redirigirá a una subsección donde se mostrarán imágenes relacionadas con esa categoría de ropa.
Vamos a crear un componente llamado /Compomnents/ClotheStyle.jsx
, que se encargará de mostrar la categoría seleccionada, como "Outerwear" o "T-shirts".
import React from 'react';
import { useParams } from 'react-router-dom';
const ClotheStyle = () => {
// Obtenemos el parámetro dinámico 'category' de la URL
const { clotheStyle } = useParams();
return (
<div>
<h2>{clotheStyle}</h2>
</div>
);
};
export default ClotheStyle;
Vamos a actualizar el archivo App.jsx
para manejar las subrutas que hemos creado dentro del componente Clothes
.
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import AppLayout from './Components/AppLayout';
import Clothes from './Components/Clothes';
import ClotheStyle from './Components/ClotheStyle';
const router = createBrowserRouter([
{
path: '/',
element: <AppLayout />,
children: [
{
path: '/:type',
element: <Clothes />,
children: [
{
path: ':clotheStyle', // Subruta dinámica para categorías como 'outerwear' o 't-shirts'
element: <ClotheStyle />,
},
],
},
],
},
]);
function App() {
return <RouterProvider router={router} />;
}
export default App;
Al tratarse de una subruta de Clothes
, se agrega como un hijo del mismo dentro de la configuración de rutas.
Vamos al componente Components/Clothes.jsx
, el objetivo es generar las subrutas para cada una de las categorías (por ejemplo, /mens/outerwear
y /mens/t-shirts
). Para esto, añadiremos nuevos enlaces y configuraremos las subrutas correspondientes.
No olvides agregar el componente
Outlet
para renderizar estas subrutas dentro de la ruta principal.
import React from 'react';
import { useParams, Link, Outlet } from 'react-router-dom';
const Clothes = () => {
const { type } = useParams();
const title = type === 'mens' ? "Men's Clothing" : "Ladies' Clothing";
return (
<div>
<h1>{title}</h1>
{/* Lista de categorías */}
<ul>
<li>
{/*Va a redireccionar a /clothes/:type/outerwear*/}
<Link to={`outerwear`}>Outerwear</Link>
</li>
<li>
{/*Va a redireccionar a /clothes/:type/t-shirts*/}
<Link to={`t-shirts`}>T-shirts</Link>
</li>
</ul>
{/* Aquí se renderizan las subrutas */}
<Outlet />
</div>
);
};
export default Clothes;
Podemos observar que ya no es necesario agregar toda la ruta, esto es gracias a la configuración que tiene por default React Router al momento de crear subrutas.
Levantamos el servidor y si has seguido los pasos correctamente, deberías ver lo siguiente:
— Ejemplo funcionando en Sandbox:
— Descarga el código
Github[tag: 1.1.0][ref]
:: Página de error genérico
React Router tiene la posibilidad de mostrar a una página genérica en caso de que no se encuentre la ruta adecuada. Esto es muy útil para manejar errores 404 o rutas no reconocidas, mejorando exponencialmente la experiencia del usuario al proporcionar una página de error personalizada en lugar de mostrar una pantalla en blanco o un error genérico del navegador.
Cuando una ruta no coincide con ninguna de las definidas en la aplicación, podemos configurar una ruta que captura todo lo que no coincide y redirigir al usuario.
Continuando con el ejemplo anterior, añadiremos el componente src/Components/NotFound.jsx
, que será el encargado de mostrar una página 404:
import React from 'react';
const NotFound = () => {
return (
<div>
<h1>Page not found :(</h1>
<p>Sorry, the page you are looking for does not exist.</p>
</div>
);
};
export default NotFound;
En el archivo App.jsx
, configuramos una ruta de todo lo que no coincide usando un comodín (*
) para redirigir a la página NotFound
cuando no se encuentre la ruta.
//...
// Importamos la página de error 404
import NotFound from './Components/NotFound';
const router = createBrowserRouter([
{
path: '/',
element: <AppLayout />,
children: [...],
},
{
path: '*', // Ruta comodín para cualquier ruta no encontrada
element: <NotFound />, // Muestra el componente NotFound
},
]);
Levantamos el servidor y si has seguido los pasos correctamente, deberías ver lo siguiente:
Podemos observar que, cuando el usuario intenta acceder a una ruta que no existe, se mostrará la página de error. Esto es funcional, pero tiene una gran limitación: el usuario no puede hacer nada desde esa pantalla para salir, solo presionar el botón de “Atrás” en el navegador. Esto puede generar una malísima experiencia de usuario si no se le proporciona una forma clara de regresar a una ruta válida.
:: Página de error por ruta y subruta
React Router nos permite definir un componente de error tanto por ruta principal como por subruta. Así como utilizamos la propiedad element
para indicar el componente a renderizar en una ruta específica, también contamos con la propiedad errorElement
. Esta propiedad nos permite manejar cualquier tipo de error o ruta no válida, asegurando que el usuario vea una página de error personalizada en caso de que ocurran problemas.
Con errorElement
, podemos gestionar errores de forma flexible en todo el árbol de rutas, mejorando la experiencia del usuario al ofrecer un mensaje claro y opciones de navegación cuando algo sale mal.
En el archivo App.jsx
, agregamos la propiedad errorElement
junto con el componente NotFound
(puede ser diferente para cada página), con la finalidad de redirigir al usuario cuando una ruta no se encuentre o cuando el valor esperado en la URL no se haya proporcionado o no sea válido. Esta configuración asegura que el usuario vea una página personalizada de "No encontrado" y no quede atrapado en una pantalla sin salida.
//...
const router = createBrowserRouter([
{
path: '/',
element: <AppLayout />,
children: [
{
path: '/:type',
element: <Clothes />,
errorElement: <NotFound />, // Redirige a la página de error 404
children: [
{
path: ':clotheStyle',
element: <ClotheStyle />,
errorElement: <NotFound />, // Redirige a la página de error 404
},
],
},
],
},
{
path: '*', // Ruta comodín para cualquier ruta no encontrada
element: <NotFound />, // Redirige a la página de error 404
},
]);
//...
Para realizar la validación de las rutas y manejar correctamente los casos en los que un parámetro no sea válido, utilizaremos la función redirect
de React Router. Esta función nos permite redirigir al usuario de forma programática a una ruta específica (en este caso, una página de error).
Nos dirigimos al componente Clothes
y aplicamos la función redirect
para redirigir al usuario en caso de que el valor dinámico de la ruta no sea válido.
import React from 'react';
import { useParams, Link, Outlet, redirect } from 'react-router-dom';
const Clothes = () => {
const { type } = useParams();
if (!['mens', 'ladies'].includes(type)) {
return redirect('/'); // Redirige a la página de error
}
...
};
export default Clothes;
De igual manera, nos dirigimos al componente ClotheStyle
y aplicamos la función redirect
para redirigir al usuario en caso de que el valor dinámico de la ruta no sea válido.
import React from 'react';
import { useParams, redirect } from 'react-router-dom';
const ClotheStyle = () => {
const { clotheStyle } = useParams();
if (!['outerwear', 't-shirts'].includes(clotheStyle)) {
return redirect('/'); // Redirige a la página de error
}
...
};
export default ClotheStyle;
Levantamos el servidor y si has seguido los pasos correctamente, deberías ver lo siguiente:
Expliquemos paso a paso:
- Cuando se hace clic en “Childs”, que corresponde a la ruta
/childs
, React Router busca internamente qué componente debe cargar. - En este caso, React Router encuentra que el componente correspondiente a esa ruta es
Clothes
. - El componente
Clothes
verifica si la condición esperada se cumple o no. - Si la condición no se cumple, el componente retorna y renderiza el componente definido en la propiedad
errorElement
. Si la condición se cumple, se carga retorna y renderiza el componente definido en la propiedadelement
.
Esto permite tener un control más preciso, ya que podemos asegurar que el usuario vea una página de error cuando ocurra un problema, sin afectar el funcionamiento de toda la aplicación.
— Ejemplo funcionando en Sandbox:
— Descarga el código
Github[tag: 1.2.0][ref]
Bibliografía y links que te puede interesar…
Iconos e imágenes…