React Router DOM v6: Navegación Declarativa y Gestión de Rutas Avanzada 🚀
Este tutorial te guiará a través de React Router DOM v6, la biblioteca esencial para gestionar la navegación en tus aplicaciones React. Aprenderás a configurar rutas, implementar navegación programática, manejar parámetros de URL, crear rutas anidadas y utilizar las nuevas características de loaders y actions para una gestión de datos más eficiente.
Introducción a React Router DOM v6 ✨
En el desarrollo de Single Page Applications (SPA) con React, la gestión de la navegación es un pilar fundamental. Los usuarios esperan una experiencia fluida, sin recargas de página completas, mientras se mueven entre diferentes vistas de tu aplicación. Aquí es donde React Router DOM brilla, proporcionando una solución declarativa y potente para mapear URLs a componentes de React.
React Router DOM ha evolucionado significativamente a lo largo de sus versiones, y la versión 6 representa un salto importante en términos de simplicidad, rendimiento y nuevas capacidades. Esta guía se centrará en la versión 6, explorando sus características clave y cómo puedes aprovecharlas para construir aplicaciones robustas y fáciles de mantener.
¿Por qué usar React Router DOM? 🤔
Las aplicaciones de una sola página (SPA) cargan una única página HTML y actualizan dinámicamente el contenido a medida que el usuario interactúa. Sin un sistema de ruteo, cada "vista" tendría que ser gestionada por lógica de estado compleja o simplemente no sería posible tener URLs diferentes para diferentes secciones. React Router DOM nos permite:
- Mantener la URL sincronizada: La barra de direcciones del navegador refleja la vista actual de la aplicación.
- Navegación declarativa: Define tus rutas como componentes de React, lo que las hace fáciles de entender y mantener.
- Gestión del historial del navegador: Soporta los botones de 'atrás' y 'adelante' del navegador de forma nativa.
- Rutas dinámicas y anidadas: Crea interfaces de usuario complejas con estructuras de navegación profundas.
- Carga de datos simplificada: Nuevas APIs para gestionar la carga y mutación de datos de forma más integrada.
Preparando el Entorno 🛠️
Antes de sumergirnos en el código, asegúrate de tener un proyecto React configurado. Si aún no tienes uno, puedes crear uno rápidamente con Vite o Create React App:
npm create vite my-react-app --template react-ts # Para TypeScript
# o
npm create vite my-react-app --template react # Para JavaScript
cd my-react-app
Ahora, instala React Router DOM:
npm install react-router-dom localforage match-sorter sort-by # localforage, match-sorter y sort-by son dependencias opcionales para usar loaders/actions con una DB en memoria
# o
yarn add react-router-dom localforage match-sorter sort-by
Conceptos Fundamentales de React Router DOM v6 📖
React Router DOM v6 introduce o refuerza varios conceptos clave que son esenciales para comprender cómo funciona la biblioteca.
1. BrowserRouter 🌐
El BrowserRouter es el componente principal que envuelve toda tu aplicación. Utiliza la API de historial HTML5 para mantener tu interfaz de usuario sincronizada con la URL. Debe ser el componente raíz de tu aplicación, o al menos el que envuelve los componentes que necesitan acceso a las funcionalidades de ruteo.
// src/main.jsx (o index.js)
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App.jsx';
import './index.css';
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>,
);
2. Routes y Route 🛣️
Routes: Este componente es el contenedor para todas tus rutas. Se encarga de buscar laRouteque coincida con la URL actual y renderiza su elemento correspondiente. Solo se renderiza unaRoutea la vez dentro deRoutes.Route: Define una ruta individual. Toma dos props principales:path(la URL a la que debe coincidir) yelement(el componente React que debe renderizarse cuando la ruta coincide).
// src/App.jsx
import { Routes, Route } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
import Contact from './pages/Contact';
function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Routes>
);
}
export default App;
3. Link y NavLink 🔗
Para la navegación entre páginas sin recargar la aplicación, usamos los componentes Link o NavLink en lugar de las etiquetas <a> HTML tradicionales.
Link: Un componente simple para navegar a otra ruta. Renderiza una etiqueta<a>sin recargar la página.NavLink: Similar aLink, pero añade automáticamente una claseactiveal elemento cuando la ruta actual coincide con sutoprop. Esto es útil para resaltar el enlace de la página activa en una barra de navegación.
// src/components/NavBar.jsx
import { NavLink } from 'react-router-dom';
function NavBar() {
return (
<nav>
<ul>
<li>
<NavLink to="/" className={({ isActive }) => (isActive ? 'active-link' : '')}>
Inicio
</NavLink>
</li>
<li>
<NavLink to="/about" className={({ isActive }) => (isActive ? 'active-link' : '')}>
Acerca de
</NavLink>
</li>
<li>
<NavLink to="/contact" className={({ isActive }) => (isActive ? 'active-link' : '')}>
Contacto
</NavLink>
</li>
</ul>
</nav>
);
}
export default NavBar;
4. useNavigate y Navegación Programática ✈️
Aunque Link y NavLink son excelentes para la navegación declarativa, a veces necesitas navegar programáticamente, por ejemplo, después de enviar un formulario o al hacer clic en un botón no-link. Para esto, usamos el hook useNavigate.
import { useNavigate } from 'react-router-dom';
function LoginForm() {
const navigate = useNavigate();
const handleSubmit = (event) => {
event.preventDefault();
// Lógica para autenticar al usuario...
const isAuthenticated = true; // Simulación
if (isAuthenticated) {
navigate('/dashboard'); // Navega a la ruta /dashboard
}
};
return (
<form onSubmit={handleSubmit}>
{/* ... campos del formulario ... */}
<button type="submit">Iniciar Sesión</button>
</form>
);
}
export default LoginForm;
El hook useNavigate devuelve una función que puedes llamar con el path al que quieres navegar. También puedes pasar un segundo argumento de objeto para opciones como replace: true (reemplaza la entrada actual en el historial en lugar de añadir una nueva).
Rutas Anidadas y Layouts 🏗️
Una de las características más potentes de React Router DOM v6 es su soporte mejorado para rutas anidadas. Esto te permite construir layouts complejos donde una parte de la interfaz de usuario cambia, mientras que otra permanece constante (como una barra lateral o un encabezado).
El Componente Outlet 🚪
Cuando defines rutas anidadas, el componente padre (el element de la Route padre) debe renderizar un <Outlet />. Este Outlet es donde se renderizará el componente de la ruta hija que coincida.
Imaginemos que tenemos un layout de panel de administración (DashboardLayout) que siempre muestra una barra lateral y un encabezado, y el contenido principal cambia según la sub-ruta.
// src/layouts/DashboardLayout.jsx
import { Outlet } from 'react-router-dom';
import SideBar from '../components/SideBar';
import Header from '../components/Header';
function DashboardLayout() {
return (
<div>
<Header />
<div style={{ display: 'flex' }}>
<SideBar />
<main style={{ flexGrow: 1, padding: '20px' }}>
<Outlet /> {/* Aquí se renderizarán las rutas hijas */}
</main>
</div>
</div>
);
}
export default DashboardLayout;
Ahora, definimos nuestras rutas anidadas en App.jsx:
// src/App.jsx (Fragmento de Routes)
import { Routes, Route } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
import Contact from './pages/Contact';
import DashboardLayout from './layouts/DashboardLayout';
import DashboardIndex from './pages/DashboardIndex';
import Profile from './pages/Profile';
import Settings from './pages/Settings';
import NotFound from './pages/NotFound'; // Componente para rutas no encontradas
function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
{/* Rutas Anidadas para el Dashboard */}
<Route path="/dashboard" element={<DashboardLayout />}>
<Route index element={<DashboardIndex />} /> {/* Ruta índice para /dashboard */}
<Route path="profile" element={<Profile />} />
<Route path="settings" element={<Settings />} />
</Route>
{/* Ruta comodín para 404 - ¡Siempre al final! */}
<Route path="*" element={<NotFound />} />
</Routes>
);
}
export default App;
Con esta configuración:
- Cuando navegas a
/dashboard, se renderizaDashboardLayout, y dentro de su<Outlet>, se renderizaDashboardIndex(gracias aindexprop). - Cuando navegas a
/dashboard/profile, se renderizaDashboardLayout, y dentro de su<Outlet>, se renderizaProfile. - Cuando navegas a
/dashboard/settings, se renderizaDashboardLayout, y dentro de su<Outlet>, se renderizaSettings.
Diagrama de Rutas Anidadas
Parámetros de URL y useParams 🆔
Frecuentemente, necesitarás pasar información dinámica a tus rutas, como el ID de un usuario o un producto. Esto se logra con parámetros de URL.
Definiendo Parámetros en la Ruta
Define un parámetro en tu path usando dos puntos (:).
// src/App.jsx (Fragmento de Routes)
// ...
<Route path="/users/:userId" element={<UserProfile />} />
// ...
Aquí, :userId es el parámetro. Cuando un usuario navega a /users/123, 123 será el valor de userId.
Accediendo a Parámetros con useParams
Dentro del componente renderizado (UserProfile en este caso), puedes acceder a estos parámetros usando el hook useParams.
// src/pages/UserProfile.jsx
import { useParams } from 'react-router-dom';
function UserProfile() {
const { userId } = useParams();
return (
<div>
<h1>Perfil del Usuario</h1>
<p>ID del Usuario: <mark>{userId}</mark></p>
{/* Aquí podrías cargar los datos del usuario usando userId */}
</div>
);
}
export default UserProfile;
Búsqueda de URL (search params) y useSearchParams 🔍
Además de los parámetros de ruta, las URLs también pueden incluir query parameters (o search params), que son pares clave-valor que aparecen después de un signo de interrogación (?). Por ejemplo: /products?category=electronics&sort=price.
El hook useSearchParams te permite leer y modificar estos parámetros de búsqueda de forma sencilla.
// src/pages/ProductList.jsx
import { useSearchParams } from 'react-router-dom';
import { useEffect } from 'react';
function ProductList() {
const [searchParams, setSearchParams] = useSearchParams();
const category = searchParams.get('category');
const sort = searchParams.get('sort');
useEffect(() => {
console.log(`Buscando productos en la categoría: ${category || 'Todas'} y ordenando por: ${sort || 'Relevancia'}`);
// Aquí podrías hacer una llamada a una API para filtrar productos
}, [category, sort]);
const handleFilterChange = (newCategory) => {
setSearchParams(prevParams => {
if (newCategory) {
prevParams.set('category', newCategory);
} else {
prevParams.delete('category');
}
return prevParams;
});
};
const handleSortChange = (newSort) => {
setSearchParams(prevParams => {
if (newSort) {
prevParams.set('sort', newSort);
} else {
prevParams.delete('sort');
}
return prevParams;
});
};
return (
<div>
<h1>Lista de Productos</h1>
<p>Categoría actual: <mark>{category || 'Todas'}</mark></p>
<p>Ordenación actual: <mark>{sort || 'Relevancia'}</mark></p>
<button onClick={() => handleFilterChange('electronics')}>Filtrar por Electrónica</button>
<button onClick={() => handleFilterChange('books')}>Filtrar por Libros</button>
<button onClick={() => handleFilterChange(null)}>Mostrar Todas las Categorías</button>
<br />
<button onClick={() => handleSortChange('price_asc')}>Ordenar por Precio Asc.</button>
<button onClick={() => handleSortChange('name_desc')}>Ordenar por Nombre Desc.</button>
</div>
);
}
export default ProductList;
El hook useSearchParams devuelve un array con dos elementos: el objeto URLSearchParams (que se comporta como un Map para leer y escribir parámetros) y una función setSearchParams similar a setState para actualizar los parámetros de búsqueda. Cuando setSearchParams es llamada, la URL se actualiza y el componente se re-renderiza.
Manejo de Errores y Rutas No Encontradas 🚫
Es crucial manejar casos donde el usuario intenta acceder a una URL que no existe.
Ruta Comodín (*) para 404
Para una ruta "no encontrada" (404), simplemente define una Route con path="*" al final de tu lista de Routes. Esto actuará como un catch-all para cualquier URL que no coincida con las rutas anteriores.
// src/App.jsx (Fragmento de Routes)
// ...
function App() {
return (
<Routes>
{/* ... Tus otras rutas ... */}
{/* Ruta comodín para 404 - ¡Siempre al final! */}
<Route path="*" element={<NotFound />} />
</Routes>
);
}
// src/pages/NotFound.jsx
function NotFound() {
return (
<div>
<h1>404 - Página No Encontrada</h1>
<p>Lo sentimos, la página que buscas no existe.</p>
<NavLink to="/">Volver al inicio</NavLink>
</div>
);
}
export default NotFound;
ErrorElement en createBrowserRouter (Recomendado para apps complejas) 🛑
Para aplicaciones más complejas, especialmente si utilizas createBrowserRouter (que se explicará a continuación), puedes definir un errorElement directamente en tus rutas. Este elemento se renderizará si ocurre un error durante la carga de datos (loader) o la ejecución de una acción (action) asociada a la ruta, o si la ruta no existe.
Las Nuevas APIs de Datos: loaders y actions (y createBrowserRouter) 📦
React Router DOM v6.4+ introdujo un conjunto de potentes APIs de datos (loaders, actions, useLoaderData, useActionData, etc.) que están inspiradas en bibliotecas como Remix. Estas APIs simplifican la carga y mutación de datos al desacoplarlas de los componentes React, mejorando el rendimiento y la experiencia del desarrollador.
Para usar estas APIs, necesitas configurar tu enrutador con createBrowserRouter en lugar de BrowserRouter.
1. createBrowserRouter 🚀
createBrowserRouter es la forma recomendada de configurar tu enrutador para aplicaciones React modernas, especialmente si necesitas las nuevas APIs de datos. Toma un array de objetos de ruta.
// src/main.jsx (o index.js)
import React from 'react';
import ReactDOM from 'react-dom/client';
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import App from './App.jsx';
import Home from './pages/Home';
import About from './pages/About';
import Contact from './pages/Contact';
import UserProfile from './pages/UserProfile';
import RootLayout from './layouts/RootLayout'; // Un layout raíz que incluye NavBar y Outlet
import NotFound from './pages/NotFound';
// Simulamos una API o base de datos en memoria para el loader/action
import { getUsers, getUserById, updateUser } from './data/users';
const router = createBrowserRouter([
{
path: '/',
element: <RootLayout />, // Este layout envuelve toda la aplicación, incluyendo el Outlet para las rutas hijas
errorElement: <NotFound />, // Elemento para errores en esta rama de rutas
children: [
{ index: true, element: <Home /> },
{ path: 'about', element: <About /> },
{ path: 'contact', element: <Contact /> },
{
path: 'users/:userId',
element: <UserProfile />,
loader: async ({ params }) => {
// Este loader se ejecuta ANTES de que UserProfile se renderice
const user = await getUserById(params.userId);
if (!user) {
throw new Response('Not Found', { status: 404 });
}
return user;
},
action: async ({ request, params }) => {
// Esta acción se ejecuta cuando se envía un formulario a esta ruta
const formData = await request.formData();
const updates = Object.fromEntries(formData);
await updateUser(params.userId, updates);
return null; // O puedes retornar datos si es necesario
},
},
],
},
]);
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>,
);
// src/layouts/RootLayout.jsx
import { Outlet } from 'react-router-dom';
import NavBar from '../components/NavBar';
function RootLayout() {
return (
<div>
<NavBar />
<main>
<Outlet /> {/* Aquí se renderizarán las rutas hijas */}
</main>
</div>
);
}
export default RootLayout;
RouterProvider es el componente que envuelve tu aplicación y le proporciona el contexto del enrutador creado por createBrowserRouter.
2. loader 📥
La función loader es un handler de datos que se ejecuta antes de que tu componente de ruta se renderice. Es el lugar ideal para buscar datos necesarios para la vista actual. Se ejecuta en el servidor (si estás usando un framework como Remix) o en el cliente antes del renderizado del componente.
- Ventajas: Los datos están listos antes de que el componente se renderice, eliminando estados de carga intermedios. Mejora la experiencia del usuario y el SEO.
- Acceso a
requestyparams: La funciónloaderrecibe un objeto conrequest(un objeto Request web estándar) yparams(los parámetros de URL). - Retorno: Debe retornar la información que el componente necesita. Puede ser un
Responseo cualquier otro dato.
// src/data/users.js (Simulación de una base de datos en memoria)
import localforage from 'localforage';
import { matchSorter } from 'match-sorter';
import sortBy from 'sort-by';
const USERS_KEY = 'users';
async function fakeNetwork(key) {
if (!key) {
window.USER_CACHE = {};
}
if (window.USER_CACHE[key]) {
return;
}
window.USER_CACHE[key] = true;
return new Promise(res => setTimeout(res, Math.random() * 800));
}
export async function getUsers(query) {
await fakeNetwork(`getUsers:${query}`);
let users = await localforage.getItem(USERS_KEY);
if (!users) users = [];
if (query) {
users = matchSorter(users, query, { keys: ['first', 'last'] });
}
return users.sort(sortBy('last', 'createdAt'));
}
export async function createUser() {
await fakeNetwork();
let users = await getUsers();
let id = Math.random().toString(36).substring(2, 9);
let user = { id, createdAt: Date.now() };
users.unshift(user);
await set(users);
return user;
}
export async function getUserById(id) {
await fakeNetwork(`user:${id}`);
let users = await localforage.getItem(USERS_KEY);
let user = users.find(user => user.id === id);
return user ?? null;
}
export async function updateUser(id, updates) {
await fakeNetwork();
let users = await localforage.getItem(USERS_KEY);
let user = users.find(user => user.id === id);
if (!user) throw new Error('No user found for', id);
Object.assign(user, updates);
await set(users);
return user;
}
function set(users) {
return localforage.setItem(USERS_KEY, users);
}
Para acceder a los datos cargados en tu componente UserProfile:
// src/pages/UserProfile.jsx
import { useLoaderData, Form } from 'react-router-dom';
import { useState } from 'react';
function UserProfile() {
const user = useLoaderData(); // Aquí recibes los datos del loader
const [isEditing, setIsEditing] = useState(false);
if (!user) {
return <div>Usuario no encontrado.</div>; // Manejo de caso donde loader devuelve null o error
}
return (
<div>
<h1>Perfil de {user.first} {user.last}</h1>
<p>ID: {user.id}</p>
<p>Email: {user.email}</p>
{isEditing ? (
<Form method="post">
<p>
<span>Nombre</span>
<input
placeholder="Primer Nombre"
aria-label="Primer nombre"
type="text"
name="first"
defaultValue={user.first}
/>
</p>
<p>
<span>Apellido</span>
<input
placeholder="Apellido"
aria-label="Apellido"
type="text"
name="last"
defaultValue={user.last}
/>
</p>
<p>
<button type="submit">Guardar</button>
<button type="button" onClick={() => setIsEditing(false)}>Cancelar</button>
</p>
</Form>
) : (
<p>
<button onClick={() => setIsEditing(true)}>Editar</button>
</p>
)}
</div>
);
}
export default UserProfile;
3. action ⬆️
La función action se ejecuta cuando se envía un formulario con un método HTTP que no sea GET (por ejemplo, POST, PUT, DELETE, PATCH) a la URL de una ruta definida con createBrowserRouter. Permite manejar mutaciones de datos de forma declarativa.
- Ventajas: Manejo directo de envíos de formularios, revalidación automática de datos después de la mutación.
- Acceso a
requestyparams: Recibe los mismos objetos que elloader. Formde React Router: Usa el componente<Form>dereact-router-dompara que el envío del formulario sea manejado por React Router en lugar de realizar una recarga completa de la página.
En el ejemplo anterior de UserProfile, hemos definido una action en la ruta /users/:userId y un <Form method="post"> en el componente UserProfile. Cuando ese formulario se envía, la action se ejecutará, actualizará los datos del usuario y React Router automáticamente revalidará los loaders de las rutas afectadas, lo que significa que el componente UserProfile se re-renderizará con los datos más recientes.
Para acceder a los datos retornados por una action (si retorna algo, aunque a menudo retorna null o una redirección):
import { useActionData } from 'react-router-dom';
function MyComponent() {
const actionData = useActionData();
// actionData contendrá lo que la función `action` de la ruta retornó.
// Por ejemplo, un mensaje de éxito o errores de validación.
return (/* ... */);
}
4. useFetcher para Cargas y Acciones sin Navegación 🔄
El hook useFetcher es increíblemente útil cuando necesitas cargar datos o realizar una acción sin cambiar la URL. Esto es perfecto para cosas como:
- "Me gusta" / "No me gusta" en una publicación.
- Cargar más elementos en una lista infinita.
- Envío de un formulario que no requiere una redirección.
import { useFetcher } from 'react-router-dom';
function LikeButton({ postId }) {
const fetcher = useFetcher();
const isLiking = fetcher.state === 'submitting';
return (
<fetcher.Form method="post" action={`/posts/${postId}/like`}>
<button type="submit" disabled={isLiking}>
{isLiking ? 'Gustando...' : 'Me Gusta'}
</button>
</fetcher.Form>
);
}
// En tu configuración de createBrowserRouter:
// {
// path: 'posts/:postId/like',
// action: async ({ request, params }) => {
// // Lógica para registrar el 'me gusta' en la base de datos
// await toggleLike(params.postId);
// return null;
// },
// },
useFetcher te da un objeto fetcher que incluye un componente <fetcher.Form> y el estado de la operación (idle, submitting, loading). Puedes usar fetcher.load() para cargar datos de un loader sin navegar.
Componentes y Hooks Adicionales Útiles 💡
useLocation 📍
El hook useLocation te devuelve el objeto location actual, que contiene información sobre la URL actual (pathname, search, hash, state).
import { useLocation } from 'react-router-dom';
function CurrentPath() {
const location = useLocation();
return (
<div>
<p>Estás en: <code>{location.pathname}</code></p>
{location.search && <p>Parámetros de búsqueda: <code>{location.search}</code></p>}
</div>
);
}
useMatch ✅
useMatch es útil para determinar si la ubicación actual coincide con una ruta específica, lo que puede ser útil para resaltar elementos de navegación complejos o aplicar lógica condicional.
import { useMatch } from 'react-router-dom';
function MyMenuItem({ to, children }) {
const match = useMatch(to);
const isActive = !!match;
return (
<li className={isActive ? 'active' : ''}>
<NavLink to={to}>{children}</NavLink>
</li>
);
}
useOutletContext (Contexto de Rutas Anidadas) 🤝
Permite que una ruta padre pase datos a sus rutas hijas. La ruta padre renderiza <Outlet context={data} /> y la ruta hija accede a esos datos con useOutletContext().
// Layout Padre
function TeamLayout() {
const teamName = 'Awesome Team';
return (
<div>
<h2>{teamName}</h2>
<Outlet context={{ teamName }} /> {/* Pasa el contexto */}
</div>
);
}
// Componente Hijo
import { useOutletContext } from 'react-router-dom';
function TeamMembers() {
const { teamName } = useOutletContext(); // Recibe el contexto
return (
<div>
<h3>Miembros de {teamName}</h3>
{/* ... */}
</div>
);
}
Consideraciones Avanzadas y Mejores Prácticas 🎯
Estructura de Archivos 📂
Una buena organización del código es clave. Considera agrupar tus rutas y componentes de página en una estructura lógica:
src/
├── components/ # Componentes reutilizables (NavBar, Header, Footer)
├── layouts/ # Componentes de layout (RootLayout, DashboardLayout)
├── pages/ # Componentes de página/ruta (Home, About, UserProfile, NotFound)
├── data/ # Módulos para simular/gestionar datos (si usas loaders/actions)
├── App.jsx # Componente principal de la aplicación
└── main.jsx # Punto de entrada para ReactDOM y RouterProvider
Optimización de Renderizado 🚀
React Router DOM v6 ya es bastante eficiente. Sin embargo, en aplicaciones muy grandes, puedes considerar técnicas como la división de código (code splitting) para cargar solo el código necesario para una ruta específica. React.lazy y Suspense son las herramientas para esto.
import React, { Suspense } from 'react';
import { Routes, Route } from 'react-router-dom';
const Home = React.lazy(() => import('./pages/Home'));
const About = React.lazy(() => import('./pages/About'));
function App() {
return (
<Routes>
<Route
path="/"
element={
<Suspense fallback={<div>Cargando Inicio...</div>}>
<Home />
</Suspense>
}
/>
<Route
path="/about"
element={
<Suspense fallback={<div>Cargando Acerca de...</div>}>
<About />
</Suspense>
}
/>
</Routes>
);
}
export default App;
Manejo de Autenticación y Rutas Protegidas 🔒
Para proteger rutas, puedes crear un componente ProtectedRoute que verifique si el usuario está autenticado. Si no lo está, lo redirige a la página de inicio de sesión.
// src/components/ProtectedRoute.jsx
import { Navigate, Outlet } from 'react-router-dom';
function ProtectedRoute({ isAuthenticated }) {
if (!isAuthenticated) {
return <Navigate to="/login" replace />; // Redirige a /login
}
return <Outlet />; // Si está autenticado, renderiza la ruta hija
}
// Uso en App.jsx con createBrowserRouter:
// {
// element: <ProtectedRoute isAuthenticated={/* tu estado de autenticación */}>
// <DashboardLayout />
// </ProtectedRoute>,
// children: [
// { path: 'dashboard', element: <DashboardIndex /> },
// { path: 'dashboard/profile', element: <Profile /> },
// ]
// },
Diagrama de Flujo de Navegación con Protección
Testing de Rutas 🧪
Cuando escribas pruebas para tus componentes que usan React Router DOM, es importante envolverlos en un MemoryRouter o BrowserRouter para que los hooks de ruteo (useParams, useNavigate, etc.) funcionen correctamente en el entorno de prueba.
import { render, screen } from '@testing-library/react';
import { BrowserRouter } from 'react-router-dom';
import UserProfile from './UserProfile';
describe('UserProfile', () => {
it('renders user ID', () => {
// Para simular useParams, puedes crear una ruta con un ID específico
render(
<BrowserRouter>
<Routes>
<Route path="/users/:userId" element={<UserProfile />} />
</Routes>
</BrowserRouter>,
{ wrapper: ({ children }) => (
<MemoryRouter initialEntries={['/users/123']}> {children} </MemoryRouter>
)}
);
// Nota: El enfoque con MemoryRouter es más directo para pruebas unitarias de componentes con rutas.
// Para el ejemplo de arriba, necesitarías simular el useLoaderData o Mockear react-router-dom
expect(screen.getByText(/ID del Usuario: 123/i)).toBeInTheDocument();
});
});
¿Por qué el `MemoryRouter` es ideal para testing?
El `MemoryRouter` no interactúa con el historial del navegador real, lo que lo hace perfecto para entornos de prueba donde no hay un navegador real o no queremos afectar el historial global. Podemos controlar su estado inicial (`initialEntries`) para simular diferentes URLs.Conclusión ✅
React Router DOM v6 es una biblioteca robusta y flexible que te permite construir experiencias de navegación ricas y dinámicas en tus aplicaciones React. Desde la configuración básica de rutas hasta el manejo de rutas anidadas, parámetros de URL y las potentes APIs de datos (loaders y actions), tienes a tu disposición un conjunto completo de herramientas para gestionar el flujo de tu aplicación.
Al adoptar los patrones modernos de React Router DOM v6, especialmente con createBrowserRouter y las nuevas APIs de datos, puedes mejorar significativamente la arquitectura de tu aplicación, su rendimiento y la experiencia de desarrollo. ¡Ahora estás listo para crear SPAs con navegación de nivel profesional! 🚀
Tutoriales relacionados
- React Query: Gestión Eficiente de Datos Asíncronos y Caché en Reactintermediate20 min
- Gestión del Estado Global en React con Context API y useReducer: Una Guía Completaintermediate15 min
- Optimización del Rendimiento en Aplicaciones React: Estrategias Avanzadas con Memoización y Virtualización de Listasadvanced18 min
Comentarios (0)
Aún no hay comentarios. ¡Sé el primero!