🎯 Objetivo del tema
El administrador aún no ha insertado un video para esta sección.
🗺️ Mapa del tema
En el Tema 3.1 hiciste tu primera app de React: una SPA con un solo componente. Hoy le agregas navegación: tu app tendrá múltiples 'páginas' (rutas) sin recargar el navegador. React Router es la librería estándar: 60k estrellas en GitHub, usada por Netflix, Airbnb, Discord, Notion. Su magia: la URL cambia, pero la página NO se recarga. Es lo que hace que una app React se sienta como una 'app de verdad' (Gmail, Twitter, etc.) y no como un sitio web estático.
El problema del 'click y no pasa nada' sin React Router.
Las 3 piezas fundamentales: el contenedor, las rutas, y el conmutador.
Cómo cambiar de página: con <Link> para links, useNavigate para código.
Cómo pasar parámetros en la URL y leerlos con useParams.
Redirigir a /login si el usuario no está autenticado.
Lista + click en país + página con más info + 404.
3.4.1 Por qué necesitas routing en una SPA
Sin React Router, una 'SPA' de React con varios componentes no tiene URLs: si el usuario hace clic en 'Ver detalle' y tú muestras un componente nuevo, la URL sigue siendo la misma. No puede compartir el link, no puede usar el botón 'Atrás' del navegador, y al recargar pierde el estado. React Router resuelve TODO: cambia la URL, mantiene el estado, permite compartir links, y soporta el botón 'Atrás' del navegador.
- 1. URL que refleja el estado: /pais/arg muestra Argentina, /pais/mex muestra México.
- 2. Botón 'Atrás' del navegador funciona: vuelve a la lista sin recargar.
- 3. Compartir links: puedes enviar https://mi-app.com/pais/arg a un amigo y ve Argentina.
- 4. Bookmarks: el usuario puede guardar páginas específicas.
- 5. SEO (con SSR): los crawlers de Google indexan cada página.
El administrador aún no ha insertado un video para esta sección.
3.4.2 BrowserRouter, Route y Routes
React Router tiene 3 piezas fundamentales: BrowserRouter envuelve toda tu app y habilita el routing. Routes (antes Switch) es el conmutador que decide qué componente mostrar según la URL. Route define cada ruta individual: path es la URL, element es el componente a renderizar. Es como un sistema de semáforos: el BrowserRouter es el sistema de semáforos, las Routes son las luces, y cada Route es una luz específica.
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { Inicio } from './pages/Inicio';
import { Sobre } from './pages/Sobre';
import { Contacto } from './pages/Contacto';
import { NoEncontrada } from './pages/NoEncontrada';
function App() {
return (
<BrowserRouter>
<nav>
<Link to="/">Inicio</Link>
<Link to="/sobre">Sobre</Link>
<Link to="/contacto">Contacto</Link>
</nav>
<Routes>
<Route path="/" element={<Inicio />} />
<Route path="/sobre" element={<Sobre />} />
<Route path="/contacto" element={<Contacto />} />
<Route path="*" element={<NoEncontrada />} /> {/* 404 */}
</Routes>
</BrowserRouter>
);
}
// Reglas de React Router v6:
// 1. BrowserRouter SIEMPRE arriba de Routes.
// 2. <Routes> contiene N <Route>.
// 3. La primera <Route> que coincide gana (orden importa).
// 4. path="*" es el catch-all (404) — va al FINAL.
// 5. element={...} recibe un JSX (no un component prop como en v5).BrowserRouter + Routes + Route: el esqueleto mínimo
El administrador aún no ha insertado un video para esta sección.
3.4.3 Link y useNavigate: navegación
Hay 2 formas de navegar en React Router: DECLARATIVA con <Link> (para links en el JSX) e IMPERATIVA con useNavigate (para navegación desde código, como después de un login exitoso). Es la misma distinción que en HTML: <a href> vs location.href. Link es declarativo, useNavigate es imperativo.
import { Link, useNavigate, useLocation } from 'react-router-dom';
// 1. Link: navegación declarativa (como <a> pero sin recargar)
function Navbar() {
return (
<nav>
<Link to="/">Inicio</Link>
<Link to="/sobre">Sobre mí</Link>
<Link to="/contacto" className="active">Contacto</Link>
</nav>
);
}
// 2. useNavigate: navegación imperativa (desde código)
function LoginForm() {
const navigate = useNavigate();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
async function handleSubmit(e) {
e.preventDefault();
const response = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
});
if (response.ok) {
navigate('/dashboard'); // redirige después del login exitoso
} else {
alert('Credenciales inválidas');
}
}
return (
<form onSubmit={handleSubmit}>
<input type="email" value={email} onChange={e => setEmail(e.target.value)} />
<input type="password" value={password} onChange={e => setPassword(e.target.value)} />
<button type="submit">Iniciar sesión</button>
</form>
);
}
// 3. NavLink: Link con clase 'active' automática cuando coincide la URL
function Navbar() {
return (
<nav>
<NavLink to="/inicio" className={({isActive}) => isActive ? 'active' : ''}>Inicio</NavLink>
<NavLink to="/sobre" className={({isActive}) => isActive ? 'active' : ''}>Sobre</NavLink>
</nav>
);
}
// 4. useNavigate con replace: reemplaza la entrada del historial (no 'Atrás')
function LogoutButton() {
const navigate = useNavigate();
const logout = () => {
localStorage.removeItem('token');
navigate('/login', { replace: true }); // reemplaza, no agrega al historial
};
return <button onClick={logout}>Cerrar sesión</button>;
}Link, useNavigate, NavLink: las 3 formas de navegar
El administrador aún no ha insertado un video para esta sección.
3.4.4 Rutas dinámicas: /pais/:id
Las rutas dinámicas son las que tienen un PARÁMETRO variable: /pais/arg, /pais/mex, /pais/col. La parte variable es el :id, que se lee con useParams. Es el patrón para páginas de detalle: lista de productos → click en uno → /producto/:id. Es la forma estándar de hacer URLs amigables y compartibles en cualquier app.
import { useParams, useNavigate, Link } from 'react-router-dom';
import { useFetch } from '../hooks/useFetch';
// Página de detalle: recibe el id del país en la URL
function DetallePais() {
const { id } = useParams(); // lee :id de la URL
const navigate = useNavigate();
const { data: pais, cargando, error } = useFetch(
`https://restcountries.com/v3.1/alpha/${id}?fields=name,capital,population,flag,region,subregion,languages,currencies,borders`
);
if (cargando) return <p>Cargando país...</p>;
if (error) return <p>Error: {error}</p>;
if (!pais) return <p>País no encontrado</p>;
const p = Array.isArray(pais) ? pais[0] : pais; // la API devuelve array o objeto
return (
<article className="pais-detalle">
<button onClick={() => navigate(-1)}>← Volver</button>
<img src={p.flag} alt={p.name.common} className="flag-grande" />
<h1>{p.name.common} ({p.name.official})</h1>
<dl>
<dt>Capital</dt><dd>{p.capital?.[0]}</dd>
<dt>Población</dt><dd>{p.population.toLocaleString()}</dd>
<dt>Región</dt><dd>{p.region} / {p.subregion}</dd>
<dt>Idiomas</dt><dd>{Object.values(p.languages || {}).join(', ')}</dd>
<dt>Monedas</dt><dd>{Object.values(p.currencies || {}).map(c => c.name).join(', ')}</dd>
</dl>
<h2>Países fronterizos:</h2>
<ul>
{(p.borders || []).map(code => (
<li key={code}><Link to={`/pais/${code}`}>{code}</Link></li>
))}
</ul>
</article>
);
}
// En el App.jsx, defines la ruta dinámica:
<Routes>
<Route path="/" element={<ListaPaises />} />
<Route path="/pais/:id" element={<DetallePais />} />
<Route path="*" element={<NoEncontrada />} />
</Routes>
// En la lista, el link al detalle:
function PaisCard({ pais }) {
return (
<Link to={`/pais/${pais.cca3}`} className="pais-card-link">
<img src={pais.flags.png} alt={pais.name.common} />
<h2>{pais.name.common}</h2>
</Link>
);
}Ruta dinámica /pais/:id con useParams y REST Countries
El administrador aún no ha insertado un video para esta sección.
3.4.5 Rutas protegidas: lógica de autenticación
Las rutas protegidas son páginas que solo usuarios autenticados pueden ver: /dashboard, /perfil, /admin. Se implementan con un componente envoltorio (Higher Order Component) que verifica si el usuario está logueado, y si no, lo redirige a /login. Es el patrón estándar para apps con auth, y se complementa con tu backend de JWT del Tema 2.10.
import { Navigate, useLocation } from 'react-router-dom';
// Componente que protege rutas: si no hay token, redirige a /login
function RutaProtegida({ children, requiereAdmin = false }) {
const token = localStorage.getItem('token');
const usuario = JSON.parse(localStorage.getItem('usuario') || 'null');
const location = useLocation();
if (!token) {
// Redirige a login, pero guarda DÓNDE quería ir el usuario para volver después
return <Navigate to="/login" state={{ from: location }} replace />;
}
if (requiereAdmin && usuario?.rol !== 'admin') {
return <Navigate to="/" replace />; // no es admin: a home
}
return children; // todo OK, renderiza la ruta
}
// Uso en App.jsx:
<Routes>
<Route path="/" element={<Inicio />} />
<Route path="/login" element={<Login />} />
<Route path="/pais/:id" element={<DetallePais />} />
{/* Rutas protegidas */}
<Route path="/perfil" element={
<RutaProtegida>
<Perfil />
</RutaProtegida>
} />
<Route path="/dashboard" element={
<RutaProtegida>
<Dashboard />
</RutaProtegida>
} />
<Route path="/admin" element={
<RutaProtegida requiereAdmin>
<AdminPanel />
</RutaProtegida>
} />
<Route path="*" element={<NoEncontrada />} />
</Routes>
// En Login, después de éxito: redirige a la página que quería ir
function Login() {
const navigate = useNavigate();
const location = useLocation();
const from = location.state?.from?.pathname || '/';
async function handleLogin() {
// ... login ...
navigate(from, { replace: true }); // vuelve a donde quería ir
}
}RutaProtegida: el componente que verifica autenticación
El administrador aún no ha insertado un video para esta sección.
3.4.6 Proyecto real: app de países con páginas de detalle
Proyecto integrador: tomaremos la app de países del Tema 3.2 y le agregaremos React Router. La lista será la home (/), cada país tendrá su página de detalle (/pais/:id), habrá una página 'Acerca de' (/sobre) y un 404 elegante. Es el patrón fundamental del 90% de las SPAs reales: lista + detalle + páginas estáticas.
Estructura de carpetas del proyecto
paises-app-con-router/
├── src/
│ ├── App.jsx # Router principal
│ ├── components/
│ │ ├── Navbar.jsx # navegación
│ │ ├── PaisCard.jsx # tarjeta de país (lista)
│ │ ├── Buscador.jsx # input de búsqueda
│ │ ├── FiltroRegion.jsx # filtro por región
│ │ └── ThemeToggle.jsx # botón tema claro/oscuro
│ ├── pages/
│ │ ├── Inicio.jsx # lista de países (home)
│ │ ├── DetallePais.jsx # /pais/:id
│ │ ├── Sobre.jsx # /sobre (estática)
│ │ └── NoEncontrada.jsx # 404
│ ├── hooks/
│ │ ├── useFetch.js # custom hook para fetch
│ │ └── usePaisesFiltrados.js # custom hook para filtrar
│ └── styles/
│ ├── tokens.css # design system
│ └── *.module.css # CSS Modules por componenteEstructura de carpetas profesional con React Router
App.jsx con todas las rutas
import { BrowserRouter, Routes, Route, NavLink } from 'react-router-dom';
import { Inicio } from './pages/Inicio';
import { DetallePais } from './pages/DetallePais';
import { Sobre } from './pages/Sobre';
import { NoEncontrada } from './pages/NoEncontrada';
import { Navbar } from './components/Navbar';
import './styles/tokens.css';
function App() {
return (
<BrowserRouter>
<Navbar />
<main className="container">
<Routes>
<Route path="/" element={<Inicio />} />
<Route path="/pais/:id" element={<DetallePais />} />
<Route path="/sobre" element={<Sobre />} />
<Route path="*" element={<NoEncontrada />} />
</Routes>
</main>
<footer>
<p>Construido con React, Vite y REST Countries API</p>
</footer>
</BrowserRouter>
);
}
function Navbar() {
return (
<nav className="navbar">
<NavLink to="/" className={({isActive}) => isActive ? 'active' : ''}>🌍 Países</NavLink>
<NavLink to="/sobre" className={({isActive}) => isActive ? 'active' : ''}>Sobre</NavLink>
</nav>
);
}App.jsx con React Router v6
El administrador aún no ha insertado un video para esta sección.
📚 Contenido ampliado
Material adicional, referencias externas verificadas y ejemplos extendidos.
🤖 AI Mission
React Router es lo que convierte un componente suelto en una aplicación.
Misión: Toma la app de países del Tema 3.2 y conviértela en una SPA multi-página: lista como home, página de detalle con info extendida del país, página 'Acerca de', 404 elegante, tema oscuro, y rutas protegidas. Pídele a la IA que revise tu estructura. Anota: ¿qué fue más difícil: rutas dinámicas, rutas protegidas, o la navegación general?
Pasos sugeridos
- Instala: npm install react-router-dom.
- Crea pages/Inicio.jsx: tu lista de países del Tema 3.2.
- Crea pages/DetallePais.jsx: useParams para leer el :id, fetch a /alpha/{id} para info extendida, muestra capital, población, región, idiomas, monedas, países fronterizos.
- Crea pages/Sobre.jsx: estática con info del proyecto.
- Crea pages/NoEncontrada.jsx: 404 con diseño amigable (no '404 error' triste).
- Modifica PaisCard.jsx: envuelve en <Link to={`/pais/${pais.cca3}`}>.
- Crea components/Navbar.jsx: usa NavLink con clase active.
- App.jsx: BrowserRouter + Routes con las 4 rutas + 404.
- Crea RutaProtegida: lógica de auth (puedes usar localStorage fake).
- Crea pages/Perfil.jsx (protegida) y agrégala a las rutas.
- Prueba: click en un país → /pais/arg → ver detalles → click en país fronterizo → /pais/bra → ver Brasil. Todo sin recargar.
- Pídele a la IA: 'Tengo esta app con React Router. Sugiere 2 mejoras: accesibilidad (skip links, focus management) o UX (breadcrumbs, scroll restoration). NO me des código, dime QUÉ agregar.'
- Aplica 2 mejoras.
- Anota: 3 cosas que aprendiste con React Router que ningún tutorial te enseñó.
📓 Entregable: URL pública de la app con routing, capturas de: home, detalle de país, países fronterizos enlazados, 404, ruta protegida, y media página de cuaderno con tu reflexión.
🚫 Errores típicos de razonamiento
Por qué: <a href> causa una RECARGA COMPLETA de la página: pierdes todo el estado, los datos cargados, la posición de scroll, y das una experiencia de página web estática. <Link to> de React Router navega sin recargar, manteniendo el estado y la experiencia de SPA. Es uno de los errores más comunes de juniors que vienen de HTML clásico. La regla: <a> para ir a OTRO sitio web (https://google.com), <Link> para navegar DENTRO de tu SPA.
Por qué: BrowserRouter DEBE envolver TODA tu app (típicamente en main.jsx o App.jsx en el top level). Si está DENTRO de Routes, las rutas internas no funcionan porque no tienen el contexto de routing. Si está FUERA pero no envuelve todo, los links y useNavigate fallan. La regla: BrowserRouter va en el componente MÁS EXTERNO (después de StrictMode y providers, pero antes de Routes).
Por qué: React Router v6 cambió Switch por Routes. Si ves tutoriales viejos con <Switch><Route component={X}/></Switch>, es v5. En v6 es <Routes><Route element={<X/>}/></Routes>. Además, v6 ya no usa exact (todas las rutas son exactas por default). Migrar de v5 a v6 es relativamente simple: cambiar Switch por Routes, component por element, y useHistory por useNavigate. Pero empieza con v6 desde el principio para evitar migración.
Por qué: Las Routes se evalúan en ORDEN y la primera que coincide gana. Si pones <Route path="/" element={<Home/>}/> ANTES de <Route path="/pais/:id" element={<Detalle/>}/>, NUNCA llegarás a la ruta del detalle, porque / matchea TODO. Pon las rutas MÁS ESPECÍFICAS primero y la / al final. O usa path="*" para el 404 al final, que solo se activa si NINGUNA ruta coincidió.
Por qué: La protección en el frontend es solo UX: oculta lo que el usuario no puede usar. Pero un usuario avanzado puede manipular el DOM, borrar el localStorage, y acceder a las rutas. La VERDADERA seguridad está en el BACKEND: tu API de Express/Supabase valida el JWT y los permisos en CADA petición, sin importar lo que el frontend muestre. Es defensa en profundidad: frontend para UX, backend para seguridad real. La regla: NUNCA confíes solo en el frontend para auth.
🧪 Laboratorio práctico
Una SPA con routing es una aplicación real. Sin routing, es un componente suelto.
Laboratorio: App de países con React Router v6 + páginas de detalle + 404 elegante
Objetivo: Convertir la app de países del Tema 3.2 en una SPA multi-página con React Router v6: home con lista, detalle con info extendida, página 'Acerca de', 404, Navbar con NavLink, y rutas protegidas (Perfil). Practicarás: BrowserRouter, Routes, Route, Link, NavLink, useParams, useNavigate, Navigate, y rutas protegidas.
Pasos
- Crea el proyecto: npm create vite@latest paises-router -- --template react.
- Instala: npm install react-router-dom.
- Crea pages/Inicio.jsx: la lista de países (mueve el código de la versión anterior).
- Crea pages/DetallePais.jsx: useParams para :id, fetch a https://restcountries.com/v3.1/alpha/{id}, muestra info extendida (capital, población, región, idiomas, monedas, países fronterizos con Link).
- Crea pages/Sobre.jsx: estática con info del proyecto, usa CSS.
- Crea pages/NoEncontrada.jsx: 404 amigable con link de vuelta al home.
- Crea pages/Perfil.jsx: contenido protegido con localStorage fake.
- Crea components/Navbar.jsx: NavLink con clase active condicional.
- Crea components/RutaProtegida.jsx: verifica token en localStorage, redirige a /login si no hay.
- App.jsx: BrowserRouter + Routes con todas las rutas + 404.
- PaisCard: Link to={`/pais/${pais.cca3}`}.
- Prueba: click en país → /pais/arg → ver detalle → click en fronterizo → /pais/bra → ver Brasil. Todo sin recargar.
- Prueba el botón 'Atrás' del navegador: debe volver a la lista sin recargar.
- Sube a Vercel. Comparte URL.
- Haz commit: 'feat: SPA multi-página con React Router v6'.
📓 Entregable: URL pública con routing funcional, capturas de: home, detalle de país con info extendida, países fronterizos enlazados, 404 elegante, ruta protegida, y commit en Git.