Routing básico
Hasta ahora has construido todo en una sola página. Pero las aplicaciones reales tienen múltiples vistas — una página de inicio, una de detalles, una de "acerca de". React Router te permite crear navegación entre diferentes "páginas" sin recargar el navegador. Esa es la magia de las SPA.
A diferencia de Vue (que tiene Vue Router integrado), React no incluye routing de serie. La librería estándar de la comunidad es React Router.
Instalar React Router
npm install react-router-dom
También puedes incluirlo al crear un proyecto nuevo. Pero como nuestro proyecto ya existe, lo instalamos manualmente.
Configurar las rutas
React Router utiliza componentes para definir las rutas. La configuración básica se hace en main.jsx:
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'
import './index.css'
import App from './App.jsx'
createRoot(document.getElementById('root')).render(
<StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</StrictMode>
)
BrowserRouter envuelve toda tu aplicación y activa el sistema de rutas. Usa el historial del navegador para URLs limpias (sin #).
Ahora define las rutas en App.jsx:
import { Routes, Route } from 'react-router-dom'
import HomeView from './views/HomeView'
import AboutView from './views/AboutView'
import CharacterListView from './views/CharacterListView'
function App() {
return (
<div>
<h1>Mi App React</h1>
<Routes>
<Route path="/" element={<HomeView />} />
<Route path="/about" element={<AboutView />} />
<Route path="/characters" element={<CharacterListView />} />
</Routes>
</div>
)
}
export default App
Cada <Route> conecta una URL (path) con un componente (element). <Routes> es el contenedor que renderiza la ruta que coincide con la URL actual.
Crear las vistas
En React, los componentes que representan páginas completas suelen ir en src/views/ (o src/pages/), mientras que los reutilizables van en src/components/:
import { Link } from 'react-router-dom'
function HomeView() {
return (
<div>
<h2>Bienvenido al Explorador Rick and Morty</h2>
<p>Descubre todos los personajes del multiverso.</p>
<Link to="/characters">Ver personajes →</Link>
</div>
)
}
export default HomeView
function AboutView() {
return (
<div>
<h2>Acerca de</h2>
<p>Esta app consume la Rick and Morty API para explorar personajes.</p>
<p>Construida con React y React Router.</p>
</div>
)
}
export default AboutView
import { useState, useEffect } from 'react'
import { Link } from 'react-router-dom'
function CharacterListView() {
const [personajes, setPersonajes] = useState([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
async function cargar() {
try {
const res = await fetch('https://rickandmortyapi.com/api/character')
const data = await res.json()
setPersonajes(data.results)
} catch (err) {
setError(err.message)
} finally {
setLoading(false)
}
}
cargar()
}, [])
if (loading) return <p>Cargando personajes...</p>
if (error) return <p style={{ color: 'red' }}>{error}</p>
return (
<div>
<h2>Personajes</h2>
<div className="grid">
{personajes.map(p => (
<div key={p.id} className="card">
<img src={p.image} alt={p.name} width={100} />
<h3>{p.name}</h3>
<Link to={`/character/${p.id}`}>Ver detalle →</Link>
</div>
))}
</div>
</div>
)
}
export default CharacterListView
Link: navegación sin recarga
<Link> es el componente de React Router para crear enlaces. A diferencia de un <a href="..."> normal, no recarga la página — simplemente actualiza la URL y renderiza el nuevo componente.
// Con un string
<Link to="/about">Acerca de</Link>
// Con una URL dinámica
<Link to={`/character/${id}`}>Ver detalle</Link>
React Router también ofrece <NavLink>, que añade automáticamente una clase CSS cuando el enlace está activo:
import { NavLink } from 'react-router-dom'
import './NavBar.css'
function NavBar() {
return (
<nav className="navbar">
<NavLink to="/" end>Inicio</NavLink>
<NavLink to="/characters">Personajes</NavLink>
<NavLink to="/about">Acerca de</NavLink>
</nav>
)
}
export default NavBar
.navbar {
display: flex;
gap: 1rem;
padding: 1rem;
background: #1a1a2e;
}
.navbar a {
color: #ccc;
text-decoration: none;
padding: 0.5rem 1rem;
border-radius: 4px;
}
.navbar a.active {
color: #fff;
background: #61DAFB;
}
NavLink añade automáticamente la clase active al enlace de la ruta actual. El atributo end en el enlace de inicio (/) asegura que solo se marque como activo en la ruta exacta, no en todas las rutas que empiezan con /.
Actualiza App.jsx para incluir la navbar:
import { Routes, Route } from 'react-router-dom'
import NavBar from './components/NavBar'
import HomeView from './views/HomeView'
import AboutView from './views/AboutView'
import CharacterListView from './views/CharacterListView'
function App() {
return (
<div>
<NavBar />
<main style={{ padding: '1rem' }}>
<Routes>
<Route path="/" element={<HomeView />} />
<Route path="/about" element={<AboutView />} />
<Route path="/characters" element={<CharacterListView />} />
</Routes>
</main>
</div>
)
}
export default App
Rutas dinámicas con useParams
Para mostrar el detalle de un personaje específico, necesitas una ruta dinámica con un parámetro. Primero, añade la ruta:
// En App.jsx, añade esta ruta dentro de <Routes>
<Route path="/character/:id" element={<CharacterDetailView />} />
El :id es un parámetro dinámico. Cuando el usuario visita /character/5, React Router captura el 5 y lo hace disponible con el hook useParams:
import { useState, useEffect } from 'react'
import { useParams, useNavigate, Link } from 'react-router-dom'
function CharacterDetailView() {
const { id } = useParams()
const navigate = useNavigate()
const [personaje, setPersonaje] = useState(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
async function cargar() {
try {
const res = await fetch(
`https://rickandmortyapi.com/api/character/${id}`
)
if (!res.ok) throw new Error(`Error: ${res.status}`)
setPersonaje(await res.json())
} catch (err) {
setError(err.message)
} finally {
setLoading(false)
}
}
cargar()
}, [id])
if (loading) return <p>Cargando personaje...</p>
if (error) return <p style={{ color: 'red' }}>{error}</p>
if (!personaje) return null
return (
<div>
<button onClick={() => navigate(-1)}>← Volver</button>
<div className="detalle">
<img src={personaje.image} alt={personaje.name} width={200} />
<h2>{personaje.name}</h2>
<ul>
<li><strong>Estado:</strong> {personaje.status}</li>
<li><strong>Especie:</strong> {personaje.species}</li>
<li><strong>Género:</strong> {personaje.gender}</li>
<li><strong>Origen:</strong> {personaje.origin.name}</li>
<li><strong>Ubicación:</strong> {personaje.location.name}</li>
</ul>
<Link to="/characters">← Volver a la lista</Link>
</div>
</div>
)
}
export default CharacterDetailView
Dos hooks clave:
useParams()— devuelve un objeto con los parámetros de la URL.{ id } = useParams()extrae el:idde la ruta.useNavigate()— devuelve una función para navegar programáticamente.navigate(-1)vuelve a la página anterior.
Navegación programática con useNavigate
No siempre navegas con enlaces. A veces necesitas redirigir desde el código:
import { useNavigate } from 'react-router-dom'
function MiComponente() {
const navigate = useNavigate()
function irAPersonaje(id) {
navigate(`/character/${id}`)
}
function volver() {
navigate(-1) // Página anterior
}
function irAInicio() {
navigate('/')
}
return (
<div>
<button onClick={irAInicio}>Ir al inicio</button>
<button onClick={() => irAPersonaje(42)}>Ver personaje 42</button>
<button onClick={volver}>Volver</button>
</div>
)
}
Ruta 404 (catch-all)
¿Qué pasa si el usuario visita una URL que no existe? Necesitas una ruta que capture todo lo que no coincida:
// En App.jsx, AL FINAL de las rutas
<Route path="*" element={<NotFoundView />} />
import { Link } from 'react-router-dom'
function NotFoundView() {
return (
<div style={{ textAlign: 'center', padding: '2rem' }}>
<h2>404 — Página no encontrada</h2>
<p>Esta dimensión no existe en el multiverso.</p>
<Link to="/">Volver al inicio</Link>
</div>
)
}
export default NotFoundView
El path="*" captura cualquier URL que no haya coincidido con las rutas anteriores. Debe ir siempre al final del bloque <Routes>.
App.jsx completo
Así queda tu aplicación con todas las rutas configuradas:
import { Routes, Route } from 'react-router-dom'
import NavBar from './components/NavBar'
import HomeView from './views/HomeView'
import AboutView from './views/AboutView'
import CharacterListView from './views/CharacterListView'
import CharacterDetailView from './views/CharacterDetailView'
import NotFoundView from './views/NotFoundView'
function App() {
return (
<div>
<NavBar />
<main style={{ padding: '1rem' }}>
<Routes>
<Route path="/" element={<HomeView />} />
<Route path="/about" element={<AboutView />} />
<Route path="/characters" element={<CharacterListView />} />
<Route path="/character/:id" element={<CharacterDetailView />} />
<Route path="*" element={<NotFoundView />} />
</Routes>
</main>
</div>
)
}
export default App
Resumen
BrowserRouterenvuelve la app enmain.jsxpara activar el sistema de rutas.<Routes>y<Route>definen qué componente se renderiza para cada URL.<Link>y<NavLink>navegan sin recargar la página.NavLinkañade claseactiveal enlace activo.- Rutas dinámicas con
:idcapturan segmentos de la URL, accesibles conuseParams(). useNavigate()permite navegación programática:navigate('/ruta')onavigate(-1)para volver.- Catch-all con
path="*"captura rutas no definidas para mostrar una página 404.
En la siguiente lección unirás todo lo aprendido en un proyecto final completo — una SPA con múltiples vistas, API, estado compartido y navegación.
Explorador del Multiverso
Construye un mini explorador de Rick and Morty con React Router y 3 rutas principales:
- Home (
/) — página de bienvenida con un enlace para ir a la lista de personajes. - CharacterList (
/characters) — obtiene los personajes de la API (https://rickandmortyapi.com/api/character) y los muestra en tarjetas. Cada tarjeta muestra la imagen, el nombre y el estado del personaje, y enlaza a su vista de detalle. - CharacterDetail (
/character/:id) — obtiene un personaje individual de la API (https://rickandmortyapi.com/api/character/:id) y muestra toda su información: imagen, nombre, estado, especie, género, origen y ubicación actual. Incluye un botón para volver atrás.
Requisitos adicionales:
- Crea un componente
NavBarconNavLinkpara Home, Personajes y Acerca de. Los enlaces activos deben tener un estilo visual diferente usando la claseactive. - Añade una ruta catch-all para mostrar una página 404 con un mensaje creativo y un enlace para volver al inicio.
- Usa
useParams()para leer los parámetros de la URL yuseNavigate()para la navegación programática (botón "volver"). - Implementa el patrón loading/error/data en las vistas que consumen la API.
lightbulb Pistas
Empieza instalando react-router-dom y envolviendo tu App con BrowserRouter en main.jsx. Recuerda que la ruta catch-all (path="*") debe ir al final dentro de <Routes>. Para la vista de detalle, usa useParams() dentro de un useEffect para construir la URL de la API. Si quieres ir más allá, crea un custom hook useApi(url) para no repetir la lógica de fetching en cada vista.