React Router te permite renderizar un componente concreto en función del "path" de la url y navegar entre componentes.
Introducción
React Router te permite renderizar un componente concreto en función del "path" de la url.
Esto se consigue creando un conjunto de rutas, en que cada ruta és un "mapping" entre el path de una URL i un componente:
flowchart LR Path --route--> Component
Sitio Web | https://react-router-7f6ae6.gitlab.io |
Proyecto | https://gitlab.com/xtec/typescript/react-router |
Entorn de trabajo
Crea un proyecto con Bun
bun create vite router --template react-swc-ts
cd router
bun update
Instala la biblioteca react-router
:
bun add react-router
bun add -d @types/react-router
Ruta
Imagina que tienes un componente Student
(modifica el fichero App.tsx
).
export default function App() {
return <Student/>
}
function Student() {
return <h1>Student</h1>
}
Inicia Vite:
bun run dev
Si abres el navegador en el path /
(http://localhost:5173/) se rendizará el componente Student
.
Lo curioso es que si abres el navegar en el path /hello
(http://localhost:5173/student) también se rendizará el componente Student
Lo que vas a hacer a continuación es que el componente App
en lugar de devolver el componente Student
devuelva el componente BrowserRouter
.
import {BrowserRouter, Routes} from "react-router"
export default function App() {
return (
<BrowserRouter>
<Routes>
</Routes>
</BrowserRouter>)
}
Como no has definido ninguna ruta, el componete BrowserRouter
devuelve un contenido vacío para el path /
: </>
Añade esta ruta a Routes
: /
-> `
import {BrowserRouter, Routes, Route} from "react-router"
export default function App() {
return (
<BrowserRouter>
<Routes>
<Route path={"/"} element={<Student/>}/>
</Routes>
</BrowserRouter>)
}
Ahora, como el componente BrowserRouter
tiene definida una ruta para el path /
que indica que tiene que devolver el componente Student
, si que se renderiza el componente Student
para la url http://localhost:5173/.
Y además, el componente Student
se renderiza únicamente para ese path a diferencia de lo que pasaba al principio.
Modifica la ruta para que el componente Student
se renderize en el path /student
.
Verifica que funciona: http://localhost:5173/student
// ...
export default function App() {
return (
<BrowserRouter>
<Routes>
<Route path={"/student"} element={<Student/>}/>
</Routes>
</BrowserRouter>)
}
Como puedes intuir, dentro del componente Routes
puedes poner más componenetes Route
que apuntan al mismo o a otros componentes.
Crea un componente Home
, y añade una ruta de /
a <Home/>
:
// ...
export default function App() {
return (
<BrowserRouter>
<Routes>
<Route path={"/"} element={<Home/>}/>
<Route path={"/student"} element={<Student/>}/>
</Routes>
</BrowserRouter>)
}
function Home() {
return <h1>Home</h1>
}
Segmento dinámico
La mayoría de los componentes utitlizan props para crear su contenido.
Modifica el componente Student
para que utilice la propiedad name
function Student({name}: { name: string }) {
return <h1>Student: {name}</h1>
}
Ahora la ruta /student
dará error porque el componente Student
requiere como parámetro la propiedad name
.
Ningun problema 🐱!
// ...
<Route path={"/student"} element={<Student name={"Laura"}/>}/>
Verifica que la ruta vuelve a funcionar: http://localhost:5173/student
El problema es que esta ruta solo sirve para Laura 👩🦰
El path de una URL está formada por segmentos que se dividen con /
, y si un segmento comienza con :
se convierte en un "segmento dinámico".
Por ejemplo, en /student/:name
, /:name
és un segmento dinámico.
Se llama dinámico porque admite cualquier valor y se proporciona al componente como un parámetro.
Modifica la ruta y elimina la prop name
del componente Student
😯
// ...
<Route path={"/student/:name"} element={<Student/>}/>
// ...
function Student() {
return (<h1>Student: Laura 👩🦰</h1>)
}
Verifica que el router funciona para cualquier path /student/:name
como http://localhost:5173/laura, http://localhost:5173/eva, etc...
... i que siempre renderiza el mismo estudiante "Laura 👩🦰"
Entonces, resulta que ahora:
- Tienes configurado el componente
BrowserRouter
con un componenteRoute
. - Para que devuelva el componene
Student
cuando el path del navegador coincida con/student/:name
. - Donde
:name
puede ser lo que el usuario quiera.
El único problema que tienes que resolver es como puede el componente Student
acceder al parámetro name
?
Para esto está la función useParams()
:
function Student() {
const {name} = useParams()
return (<h1>Student: {name}</h1>)
}
I ahora si vas a http://localhost:5173/eva la pagina renderiza a la estudiante "eva 👩🦰"
Crea el fichero data.ts
con algunos estudiantes:
export type Student = {
id: number
emoji: string
name: string
surname: string
}
export const students: Student[] = [
{id: 1, emoji: "👩🦰", name: "Mary", surname: "Sinclair"},
{id: 2, emoji: "🧙♂️", name: "John", surname: "Smith"},
{id: 3, emoji: "🐱", name: "Olivia", surname: "Hart"}
]
Modifica la ruta de /student/:name
a /student/:id
para utilizar el id
de estudiante en lugar del name
:
// ...
<Route path={"/student/:id"} element={<Student/>}/>
Modifica el componente Student
para que busque el estudiante en la lista students
de data.tsx
:
import {students} from "./data.ts";
// ...
function Student() {
const { id } = useParams()
const student = students.find(student => student.id == Number(id))
if (student == null) {
return <p>Student not found!</p>
}
return <p>{student.emoji} {student.name} {student.surname}</p>
}
Verifica que ahora esta url http://localhost:5173/student/1 responde con "👩🦰 Mary Sinclair"
Añade una nueva ruta:
export default function App() {
return (
<BrowserRouter>
<Routes>
<Route path={"/"} element={<Home/>}/>
<Route path={"/student/"} element={<Students/>}/>
<Route path={"/student/:id"} element={<Student/>}/>
</Routes>
</BrowserRouter>)
}
Crea el componente Students
que devuelva una tabla con todos los estudiantes:
ID | Emoji | Surname | Name |
---|---|---|---|
1 | 👩🦰 | Sinclair | Mary |
2 | 🧙♂️ | Smith | John |
3 | 🐱 | Hart | Olivia |
function Students() {
return (
<div className="container m-5 w-50">
<h3>Students</h3>
<table className={"table table-bordered mt-5"}>
<tr><th>ID</th><th>Emoji</th><th>Surname</th><th>Name</th></tr>
{students.map(student => <tr>
<td>{student.id}</td>
<td className={"fs-4"}>{student.emoji}</td>
<td>{student.surname}</td>
<td>{student.name}</td>
</tr>)}
</table>
</div>
)
}
Link
Una aplicación debe tener enlaces para que el usuario pueda navegar de una página a otra página.
Modifica el componente Home
y añade un enlace normal a /students
:
function Home() {
return <a href="/student/">Students</a></li>
}
Pero al ejemplo le falta un poco de estilo 🧙♂️ 💫
function Home() {
return (
<div className={"container m-5"}>
<ul className="nav nav-underline mb-3 justify-content-center">
<li className="nav-item">
<a className="nav-link active" aria-current="page" href={"/"}>Home</a>
</li>
<li className="nav-item">
<a className="nav-link active" href={"/student/"}>Students</a>
</li>
</ul>
<h3>Patufet School</h3>
</div>
)
Cuando el usuaio hace clic en el enlace, el servidor responde devolvieno otra vez toda la aplicación, y como el path a cambiiado BrowserRouter
devuelve el componente correspodiente.
De funcionar funciona, funciona bien de momento, pero no es nada eficiente 🤔!
React router proporciona el componente Link
, que es un "wrapper" de <a>
, que lo que hace es impedir que el navegador ejecute el enlace, y en su lugar le dice al BrowserRouter
que actualice el contenido a la nueva ruta.
De esa manera, aunque parezca que estamos navegndo de página a página, en realidad estamos navegando de componente a componente 😀!
Modifica el enlace:
function Home() {
return (
// ...
<Link className="nav-link active" aria-current="page" to={"/"}>Home</Link>
}
Ves al home (/
), para el servidor, y verifica que los enlaces funcionan!
19:09:29 [vite] (client) hmr update /src/App.tsx (x22)
error: script "dev" exited with code 58
PS C:\Users\david\react-router>
Este tipo de navegación se llama "client-site routing" porque, aunque el navegador muestre una url diferente, estas navegando de un componente a otro componente detro de la aplicación.
Vuelve a arrancar el servidor:
> bun run dev
...
A continuación modifica el componente <Students>
de tal manera que cada estudiante de la tabla tenga un enlace a la ruta <Route path={"/student/:id"} element={<Student/>}/>
.
De esta manera el usuario puede hacer clic y ir a la "pàgina" del estudiante:
// ...
{students.map(student => <tr>
<td>
<Link to={"/student/" + student.id} className={"btn btn-primary btn-large"}>{student.id}</Link>
</td>
<td className={"fs-4"}>{student.emoji}</td>
Navbar
Crea un componente Navbar
con un barra de navegación de Boostrap - Navbar
import {NavLink} from "react-router";
export default function Navbar() {
return (
<nav className="navbar navbar-expand-lg bg-body-tertiary">
<div className="container-fluid">
<NavLink className="navbar-brand" to="/">Patufet</NavLink>
// ...
Añade el componente Navbar
a los componentes Home
, Student
i Students
.
function Home() {
return (
<>
<Navbar/>
<div className={"container m-5"}>
// ...
Página web
Com has visto, React te permite construir aplicaciones clientes que no necesitan un servidor.
Abre el terminal y ejecuta:
> bun run build
vite v6.0.3 building for production...
✓ 26 modules transformed.
dist/index.html 0.48 kB │ gzip: 0.32 kB
dist/assets/index-COkpbCJz.js 145.81 kB │ gzip: 47.24 kB
✓ built in 707ms
En el directorio dist
están todos los ficheros necesarios para ejectuar la aplicación.
A continuación subirás el contenido a un alojamiento web:
Regístrate en Netlifly.
Crea un "new team" con un plan grautio:
Sube el directorio dist
que acabas de crear:
Ves a la página del sitio (y cambia el nombre si quieres):
En el aparece la URL del sitio accesible desde cualquier navegador conectado a Internet!
Actividades
Teachers
Modifica el fichero data.ts
y añade unos cuantos profesores a la escuela:
export type Teacher = {
id: string,
name: string,
avatar: string
}
export const teachers: Teacher[] = [
{id: "pmoretti", name: "Pau Moretti", avatar: "https://i.pravatar.cc/100?img=11"},
{id: "lsoler", name: "Laia Soler", avatar: "https://i.pravatar.cc/100?img=5"},
{id: "msimon", name: "Marc Simon", avatar: "https://i.pravatar.cc/100?img=8"},
]
Crea el fichero teachers.tsx
con los componentes Teacher
y Teachers
:
export function Teachers() {
return (
<div className="container py-4">
<h2 className="text-center mb-4">Teachers</h2>
// ...
</div>
)
}
// ...
Añade las rutas /teacher/
y /teacher/:id
al componete BrowserRouter
.
export default function App() {
return (
<BrowserRouter>
<Menu />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/student/" element={<Students />} />
<Route path="/student/:id" element={<Student />} />
<Route path="/teacher/" element={<Teachers />} />
<Route path="/teacher/:id" element={<Teachers />} />
</Routes>
</BrowserRouter>
)
}
Sites
Añade una ruta "Not Found"
export default function App() {
return (
<BrowserRouter>
<Routes>
<Route path="*" element={<NotFound />} />
<Route path="/" element={<Home />} />
// ..
}
function NotFound() {
return (
<>
<p className="text-danger fs-4">404 Not Found</p>
</>
)
}
Añade alguna mejoras, por ejemplo:
- Una pàgina con un mapa de donde está la escuela.
- etc.
Despliega la nueva version en Netlify.