Skip to content

11. React Router

React Router es la librería estándar para manejar navegación en aplicaciones React. Permite crear SPAs (Single Page Applications) con múltiples “páginas” sin recargar el navegador.

CaracterísticaDescripción
NavegaciónSin recargar la página
URLsURLs amigables y compartibles
HistorialBotones atrás/adelante funcionan
ParámetrosURLs dinámicas con datos
Instalar React Router
npm install react-router-dom
Configuración en main.jsx
// main.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);

Definir rutas
import { Routes, Route } from 'react-router-dom';
// Componentes de página
function Inicio() {
return <h1>Página de Inicio</h1>;
}
function Acerca() {
return <h1>Acerca de Nosotros</h1>;
}
function Contacto() {
return <h1>Contacto</h1>;
}
// App con rutas
function App() {
return (
<Routes>
<Route path="/" element={<Inicio />} />
<Route path="/acerca" element={<Acerca />} />
<Route path="/contacto" element={<Contacto />} />
</Routes>
);
}
Ruta 404
import { Routes, Route } from 'react-router-dom';
function NotFound() {
return (
<div>
<h1>404 - Página no encontrada</h1>
<p>La página que buscas no existe.</p>
</div>
);
}
function App() {
return (
<Routes>
<Route path="/" element={<Inicio />} />
<Route path="/acerca" element={<Acerca />} />
{/* Ruta comodín para 404 */}
<Route path="*" element={<NotFound />} />
</Routes>
);
}

Componente Link
import { Link } from 'react-router-dom';
function Navegacion() {
return (
<nav>
{/* Link en lugar de <a href=""> */}
<Link to="/">Inicio</Link>
<Link to="/acerca">Acerca</Link>
<Link to="/contacto">Contacto</Link>
</nav>
);
}
// ❌ NO usar <a href="/acerca"> - recarga la página
// ✅ Usar <Link to="/acerca"> - navegación SPA
NavLink con estilos activos
import { NavLink } from 'react-router-dom';
function Navegacion() {
return (
<nav>
<NavLink
to="/"
className={({ isActive }) => isActive ? "activo" : ""}
>
Inicio
</NavLink>
<NavLink
to="/acerca"
className={({ isActive }) => isActive ? "activo" : ""}
>
Acerca
</NavLink>
<NavLink
to="/contacto"
style={({ isActive }) => ({
color: isActive ? "red" : "blue",
fontWeight: isActive ? "bold" : "normal"
})}
>
Contacto
</NavLink>
</nav>
);
}
Layout con navegación
import { Routes, Route, Link } from 'react-router-dom';
function Layout({ children }) {
return (
<div>
<header>
<nav>
<Link to="/">Inicio</Link>
<Link to="/productos">Productos</Link>
<Link to="/contacto">Contacto</Link>
</nav>
</header>
<main>{children}</main>
<footer> 2024 Mi App</footer>
</div>
);
}
function App() {
return (
<Layout>
<Routes>
<Route path="/" element={<Inicio />} />
<Route path="/productos" element={<Productos />} />
<Route path="/contacto" element={<Contacto />} />
</Routes>
</Layout>
);
}

Parámetros de ruta
import { Routes, Route, useParams } from 'react-router-dom';
// Componente que usa el parámetro
function DetalleProducto() {
// Obtener parámetro de la URL
const { id } = useParams();
return (
<div>
<h1>Producto #{id}</h1>
{/* Aquí harías fetch del producto con ese ID */}
</div>
);
}
// Definir ruta con parámetro
function App() {
return (
<Routes>
<Route path="/productos" element={<ListaProductos />} />
<Route path="/productos/:id" element={<DetalleProducto />} />
</Routes>
);
}
// URLs que coinciden:
// /productos/1 → id = "1"
// /productos/abc → id = "abc"
// /productos/123 → id = "123"
Múltiples parámetros
import { useParams } from 'react-router-dom';
function ArticuloCategoria() {
const { categoria, slug } = useParams();
return (
<div>
<p>Categoría: {categoria}</p>
<p>Artículo: {slug}</p>
</div>
);
}
// Ruta
<Route path="/blog/:categoria/:slug" element={<ArticuloCategoria />} />
// URL: /blog/tecnologia/react-hooks
// categoria = "tecnologia"
// slug = "react-hooks"
Fetch con parámetro
import { useState, useEffect } from 'react';
import { useParams, Link } from 'react-router-dom';
function DetalleUsuario() {
const { id } = useParams();
const [usuario, setUsuario] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch(`https://jsonplaceholder.typicode.com/users/${id}`)
.then(res => res.json())
.then(data => {
setUsuario(data);
setLoading(false);
});
}, [id]); // Re-fetch cuando cambia el ID
if (loading) return <p>Cargando...</p>;
return (
<div>
<Link to="/usuarios">← Volver</Link>
<h1>{usuario.name}</h1>
<p>Email: {usuario.email}</p>
<p>Teléfono: {usuario.phone}</p>
</div>
);
}

Query parameters
import { useSearchParams } from 'react-router-dom';
function BusquedaProductos() {
const [searchParams, setSearchParams] = useSearchParams();
// Leer parámetros
const query = searchParams.get('q') || '';
const categoria = searchParams.get('categoria') || 'todas';
const pagina = searchParams.get('pagina') || '1';
// Actualizar parámetros
const buscar = (termino) => {
setSearchParams({ q: termino, categoria, pagina: '1' });
};
const cambiarCategoria = (cat) => {
setSearchParams({ q: query, categoria: cat, pagina: '1' });
};
return (
<div>
<input
value={query}
onChange={e => buscar(e.target.value)}
placeholder="Buscar..."
/>
<select
value={categoria}
onChange={e => cambiarCategoria(e.target.value)}
>
<option value="todas">Todas</option>
<option value="electronica">Electrónica</option>
<option value="ropa">Ropa</option>
</select>
<p>Buscando: "{query}" en {categoria}</p>
</div>
);
}
// URL resultante: /productos?q=laptop&categoria=electronica&pagina=1

useNavigate
import { useNavigate } from 'react-router-dom';
function FormularioLogin() {
const navigate = useNavigate();
const handleSubmit = async (e) => {
e.preventDefault();
// Simular login
const exito = await login(email, password);
if (exito) {
// Navegar a dashboard
navigate('/dashboard');
// O reemplazar historial (no puede volver atrás)
// navigate('/dashboard', { replace: true });
}
};
return (
<form onSubmit={handleSubmit}>
{/* ... campos ... */}
<button type="submit">Iniciar Sesión</button>
</form>
);
}
Navegar con state
import { useNavigate, useLocation } from 'react-router-dom';
// Enviar datos
function ListaProductos() {
const navigate = useNavigate();
const verDetalle = (producto) => {
navigate(`/productos/${producto.id}`, {
state: { producto } // Pasar datos
});
};
return (
<ul>
{productos.map(p => (
<li key={p.id}>
{p.nombre}
<button onClick={() => verDetalle(p)}>Ver</button>
</li>
))}
</ul>
);
}
// Recibir datos
function DetalleProducto() {
const location = useLocation();
const producto = location.state?.producto;
if (!producto) {
return <p>Producto no encontrado</p>;
}
return <h1>{producto.nombre}</h1>;
}
Navegar historial
import { useNavigate } from 'react-router-dom';
function Navegacion() {
const navigate = useNavigate();
return (
<div>
<button onClick={() => navigate(-1)}>← Atrás</button>
<button onClick={() => navigate(1)}>Adelante →</button>
<button onClick={() => navigate('/')}>Ir a Inicio</button>
</div>
);
}

Rutas anidadas
import { Routes, Route, Outlet, Link } from 'react-router-dom';
// Layout del dashboard
function DashboardLayout() {
return (
<div className="dashboard">
<aside>
<nav>
<Link to="/dashboard">Resumen</Link>
<Link to="/dashboard/perfil">Perfil</Link>
<Link to="/dashboard/configuracion">Configuración</Link>
</nav>
</aside>
<main>
{/* Aquí se renderizan las rutas hijas */}
<Outlet />
</main>
</div>
);
}
// Componentes hijos
function DashboardResumen() {
return <h1>Resumen del Dashboard</h1>;
}
function DashboardPerfil() {
return <h1>Mi Perfil</h1>;
}
function DashboardConfig() {
return <h1>Configuración</h1>;
}
// Definir rutas anidadas
function App() {
return (
<Routes>
<Route path="/" element={<Inicio />} />
{/* Ruta padre con layout */}
<Route path="/dashboard" element={<DashboardLayout />}>
{/* Rutas hijas */}
<Route index element={<DashboardResumen />} />
<Route path="perfil" element={<DashboardPerfil />} />
<Route path="configuracion" element={<DashboardConfig />} />
</Route>
</Routes>
);
}
Rutas protegidas
import { Navigate, Outlet } from 'react-router-dom';
// Componente para proteger rutas
function RutaProtegida({ isAuthenticated }) {
if (!isAuthenticated) {
// Redirigir a login si no está autenticado
return <Navigate to="/login" replace />;
}
// Si está autenticado, mostrar contenido
return <Outlet />;
}
// Uso en las rutas
function App() {
const [isAuthenticated, setIsAuthenticated] = useState(false);
return (
<Routes>
<Route path="/" element={<Inicio />} />
<Route path="/login" element={<Login />} />
{/* Rutas protegidas */}
<Route element={<RutaProtegida isAuthenticated={isAuthenticated} />}>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/perfil" element={<Perfil />} />
<Route path="/admin" element={<Admin />} />
</Route>
</Routes>
);
}

🐝