A la lliçó anterior va quedar clar què és l'enrutament al client, per què React no el porta de sèrie i quina de les tres formes d'usar React Router v7 fa servir aquest curs: el mode de dades, amb createBrowserRouter i RouterProvider. Ara toca escriure'l. En aquesta lliçó converteixes CicloUrbano d'una aplicació d'una sola pantalla en una aplicació enrutada: instal·laràs el paquet, crearàs el mapa de rutes a src/rutes.jsx, muntaràs el proveïdor de l'enrutador a main.jsx sense trencar l'ordre de proveïdors que ja existeix, reorganitzaràs el projecte amb una carpeta src/pagines/, substituiràs els <a href="#…"> de la Capcalera per enllaços de veritat —saldant per fi el deute del mòdul 2—, llegiràs l'identificador de la fitxa amb useParams i traslladaràs el filtre del SelectorTipus des d'useState fins a la mateixa URL amb useSearchParams. En acabar, cada pantalla de CicloUrbano tindrà la seva adreça.
Contingut
- Punt de partida i pla de la reorganització
- Instal·lació del paquet
- La carpeta
src/pagines/i què es queda asrc/components/ - El mapa de rutes:
src/rutes.jsx - Muntar l'enrutador a
main.jsx - Per què els proveïdors van fora de l'enrutador
- Enllaços:
<Link>enfront de<a href> <NavLink>i la classe activa: la nav deCapcalera- Segments dinàmics i
useParams - Què fer quan l'identificador no existeix
- Paràmetres de consulta amb
useSearchParams - La ruta comodí i la pàgina de no trobat
- Com tria React Router la millor coincidència
- Punt de partida i pla de la reorganització
Així és avui App.jsx, al final del mòdul 5: una única pantalla que mostra el catàleg amb el seu cercador, el seu filtre i el seu formulari de reserva, tot embolcallat en Disseny.
// src/App.jsx — el punt de partida (versió resumida)
import Disseny from './components/Disseny.jsx';
import CercadorBicicletes from './components/CercadorBicicletes.jsx';
import SelectorTipus from './components/SelectorTipus.jsx';
import LlistaBicicletes from './components/LlistaBicicletes.jsx';
import PanellReserva from './components/PanellReserva.jsx';
import { useState } from 'react';
import { bicicletes, estacions } from './dades/domini.js';
function App() {
const [terme, setTerme] = useState('');
const [tipusTriat, setTipusTriat] = useState('todos');
const [idSeleccionada, setIdSeleccionada] = useState(null);
const visibles = bicicletes
.filter((bici) => tipusTriat === 'todos' || bici.tipus === tipusTriat)
.filter((bici) => bici.model.toLowerCase().includes(terme.toLowerCase()));
return (
<Disseny>
<CercadorBicicletes terme={terme} alCanviarTerme={setTerme} />
<SelectorTipus tipusTriat={tipusTriat} alCanviarTipus={setTipusTriat} />
<LlistaBicicletes
bicicletes={visibles}
estacions={estacions}
idSeleccionada={idSeleccionada}
alSeleccionar={setIdSeleccionada}
/>
<PanellReserva />
</Disseny>
);
}
export default App;El pla d'aquesta lliçó, en ordre:
flowchart LR
A["1. npm install<br/>react-router"] --> B["2. Crear src/pagines/<br/>amb una pantalla per ruta"]
B --> C["3. src/rutes.jsx<br/>createBrowserRouter"]
C --> D["4. main.jsx<br/>RouterProvider"]
D --> E["5. Capcalera<br/>NavLink"]
E --> F["6. useParams<br/>useSearchParams"]
En acabar, App.jsx desapareix com a pantalla: el seu contingut passa a PaginaCataleg i el paper d'arrel l'assumeix l'enrutador. És la reestructuració més gran del curs fins ara, així que convé fer-la per passos i comprovar que l'aplicació arrenca a cadascun.
- Instal·lació del paquet
Això és tot. Ni complements de Vite, ni configuració addicional: en el mode de dades, React Router és una dependència normal.
Sobre el nom del paquet, perquè és la primera confusió amb què et trobaràs:
| Paquet | Versió | Què és |
|---|---|---|
react-router-dom |
v6 i anteriors | El paquet que s'instal·lava en aplicacions web. Exportava BrowserRouter, Link, etc. |
react-router |
v6 i anteriors | El nucli independent de plataforma. No s'instal·lava directament |
react-router |
v7 | L'únic paquet que necessites. D'aquí surt tot |
react-router-dom |
v7 | Es continua publicant, però només reexporta react-router. Existeix per no trencar projectes migrats |
Conseqüència pràctica: tots els exemples d'internet que vegis amb from 'react-router-dom' funcionen igual canviant la importació a from 'react-router'. Tria'n un i sigues coherent; tenir-los tots dos instal·lats és una font d'errors de context molt difícils de diagnosticar, perquè pots acabar amb dues còpies de l'enrutador en memòria.
Comprova la versió instal·lada:
npm list react-router
# [email protected]
# └── [email protected]
- La carpeta
src/pagines/ i què es queda a src/components/
src/pagines/ i què es queda a src/components/Abans d'escriure el mapa cal decidir on viu cada cosa. Introduïm una carpeta nova:
src/ ├── components/ ← peces reutilitzables d'interfície ├── contextos/ ├── dades/ ├── hooks/ ├── pagines/ ← NOU: una pantalla per ruta ├── reductors/ ├── utilitats/ ├── main.jsx └── rutes.jsx ← NOU: el mapa de rutes
La regla que separa totes dues carpetes:
src/pagines/ |
src/components/ |
|
|---|---|---|
| Què és | La pantalla completa associada a una ruta | Una peça d'interfície reutilitzable |
| Qui la renderitza | L'enrutador, i només ell | Altres components o pàgines |
| Quantes vegades apareix | Una, a la seva ruta | Les que calgui |
Pot llegir useParams |
Sí, és el seu lloc natural | Preferiblement no: rep-lo per props |
| Exemples | PaginaCataleg, PaginaAcces |
TargetaBicicleta, Avis, Modal |
Aquest darrer punt és més que una convenció d'ordre: una pàgina pot dependre de l'enrutador; un component reutilitzable, com menys millor. Si TargetaBicicleta crida useParams() per dins, deixa de poder-se fer servir en una pantalla que no tingui aquest paràmetre, i provar-la al mòdul 9 obligarà a embolcallar-la en un enrutador fals. La pàgina llegeix el paràmetre i l'hi passa per props: el component continua sent una funció dels seus props, com a 02-03.
Aquestes són les pàgines de CicloUrbano. Comencem amb versions mínimes i les anirem completant:
// src/pagines/PaginaCataleg.jsx
import { useState } from 'react';
import CercadorBicicletes from '../components/CercadorBicicletes.jsx';
import SelectorTipus from '../components/SelectorTipus.jsx';
import LlistaBicicletes from '../components/LlistaBicicletes.jsx';
import { bicicletes, estacions } from '../dades/domini.js';
function PaginaCataleg() {
const [terme, setTerme] = useState('');
const [tipusTriat, setTipusTriat] = useState('todos');
const visibles = bicicletes
.filter((bici) => tipusTriat === 'todos' || bici.tipus === tipusTriat)
.filter((bici) => bici.model.toLowerCase().includes(terme.toLowerCase()));
return (
<section>
<h2>Catàleg de bicicletes</h2>
<CercadorBicicletes terme={terme} alCanviarTerme={setTerme} />
<SelectorTipus tipusTriat={tipusTriat} alCanviarTipus={setTipusTriat} />
<LlistaBicicletes bicicletes={visibles} estacions={estacions} />
</section>
);
}
export default PaginaCataleg;// src/pagines/PaginaEstacions.jsx
import TargetaEstacio from '../components/TargetaEstacio.jsx';
import { estacions, bicicletes } from '../dades/domini.js';
function PaginaEstacions() {
return (
<section>
<h2>Estacions</h2>
<ul>
{estacions.map((estacio) => (
<li key={estacio.id}>
<TargetaEstacio
estacio={estacio}
bicicletesEnEstacio={bicicletes.filter((b) => b.estacioId === estacio.id)}
/>
</li>
))}
</ul>
</section>
);
}
export default PaginaEstacions;// src/pagines/PaginaReserves.jsx
import { useReserves } from '../contextos/ContextReserves.jsx';
import PanellReserves from '../components/PanellReserves.jsx';
function PaginaReserves() {
const { estat } = useReserves();
return (
<section>
<h2>Les meves reserves</h2>
<PanellReserves reserves={estat.reserves} />
</section>
);
}
export default PaginaReserves;PaginaAcces la desenvoluparàs a 06-05 (necessita iniciarSessio i el retorn a la destinació original), així que de moment n'hi ha prou amb un esquelet. I falta la més important per a aquesta lliçó:
// src/pagines/PaginaNoTrobada.jsx
import { Link } from 'react-router';
function PaginaNoTrobada() {
return (
<section>
<h2>Aquesta pàgina no existeix</h2>
<p>
L'adreça que has escrit no correspon a cap pantalla de CicloUrbano.
Pot ser que l'enllaç estigui desactualitzat o que hi hagi un error tipogràfic.
</p>
<Link to="/">Tornar al catàleg</Link>
</section>
);
}
export default PaginaNoTrobada;
- El mapa de rutes:
src/rutes.jsx
src/rutes.jsxAquí hi ha el cor del mode de dades: el mapa és un array d'objectes JavaScript, no marcatge JSX.
// src/rutes.jsx
import { createBrowserRouter } from 'react-router';
import PaginaCataleg from './pagines/PaginaCataleg.jsx';
import PaginaFitxaBicicleta from './pagines/PaginaFitxaBicicleta.jsx';
import PaginaEstacions from './pagines/PaginaEstacions.jsx';
import PaginaDetallEstacio from './pagines/PaginaDetallEstacio.jsx';
import PaginaReserves from './pagines/PaginaReserves.jsx';
import PaginaNovaReserva from './pagines/PaginaNovaReserva.jsx';
import PaginaAcces from './pagines/PaginaAcces.jsx';
import PaginaTaller from './pagines/PaginaTaller.jsx';
import PaginaNoTrobada from './pagines/PaginaNoTrobada.jsx';
export const router = createBrowserRouter([
{ path: '/', element: <PaginaCataleg /> },
{ path: '/bicicletas/:bicicletaId', element: <PaginaFitxaBicicleta /> },
{ path: '/estaciones', element: <PaginaEstacions /> },
{ path: '/estaciones/:estacionId', element: <PaginaDetallEstacio /> },
{ path: '/reservas', element: <PaginaReserves /> },
{ path: '/reservas/nueva', element: <PaginaNovaReserva /> },
{ path: '/acceso', element: <PaginaAcces /> },
{ path: '/taller', element: <PaginaTaller /> },
{ path: '*', element: <PaginaNoTrobada /> }
]);Desgranem les decisions:
- El fitxer és
.jsx, no.js. Conté JSX a la propietatelement, així que l'extensió ho ha de permetre o Vite es queixarà. pathés el patró d'URL, amb/inicial a les rutes de primer nivell. Els segments que comencen per:són dinàmics.elementrep un element de React ja creat (<PaginaCataleg />), no el component (PaginaCataleg). És una diferència subtil però estricta: passar el component sense instanciar és un error clàssic i produeix una pantalla en blanc.- S'exporta
routeramb nom, seguint la convenció del projecte:export defaultper a components, exportació amb nom per a la resta. createBrowserRouteres crida una sola vegada, fora de qualsevol component. Si el criessis dins d'un component, cada render crearia un enrutador nou i perdries tot l'estat de navegació.- La ruta
*va al final per llegibilitat, no per necessitat: l'ordre ja no decideix res (apartat 13).
Encara no hi ha imbricació. Disseny encara no és al mapa, així que les pantalles es pinten soltes, sense capçalera ni peu. És un estat intermedi deliberat: ho arreglaràs a 06-03, que és justament la lliçó de rutes imbricades.
- Muntar l'enrutador a
main.jsx
main.jsxRecorda com estava el punt d'entrada al final del mòdul 5:
// src/main.jsx — ABANS
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App.jsx';
import LimitError from './components/LimitError.jsx';
import { ProveidorTema } from './contextos/ContextTema.jsx';
import { ProveidorUsuari } from './contextos/ContextUsuari.jsx';
import { registrarError } from './utilitats/monitoritzacio.js';
import './index.css';
createRoot(document.getElementById('root')).render(
<StrictMode>
<LimitError titol="CicloUrbano no està disponible ara mateix" alRegistrar={registrarError}>
<ProveidorTema>
<ProveidorUsuari>
<App />
</ProveidorUsuari>
</ProveidorTema>
</LimitError>
</StrictMode>
);El canvi consisteix a substituir <App /> per <RouterProvider router={router} />:
// src/main.jsx — DESPRÉS
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { RouterProvider } from 'react-router';
import { router } from './rutes.jsx';
import LimitError from './components/LimitError.jsx';
import { ProveidorTema } from './contextos/ContextTema.jsx';
import { ProveidorUsuari } from './contextos/ContextUsuari.jsx';
import { registrarError } from './utilitats/monitoritzacio.js';
import './index.css';
createRoot(document.getElementById('root')).render(
<StrictMode>
<LimitError titol="CicloUrbano no està disponible ara mateix" alRegistrar={registrarError}>
<ProveidorTema>
<ProveidorUsuari>
<RouterProvider router={router} />
</ProveidorUsuari>
</ProveidorTema>
</LimitError>
</StrictMode>
);Punts que mereixen comentari:
RouterProviderno acceptachildren. Tot el que es pinta a sota surt del mapa de rutes. Si escrius<RouterProvider router={router}><Algo /></RouterProvider>, aquest<Algo />no apareixerà mai.App.jsxja no s'importa. El pots esborrar o deixar-lo buit; el seu paper l'acompleixen ararutes.jsx(què es pinta) iDisseny(com s'enquadra, a partir de 06-03).StrictModecontinua a l'arrel, com des de 01-03. Continuarà provocant el doble muntatge en desenvolupament, i això inclou els efectes de les pàgines: res de nou respecte a 05-02.ProveidorReservesiProveidorAvisosno apareixen aquí. A 06-03 els col·locaràs dins deDisseny, la ruta arrel, junt amb la resta del marc de l'aplicació. També podries pujar-los amain.jsx; la decisió es justifica a la lliçó següent.
Amb això ja pots arrencar npm run dev, escriure http://localhost:5173/estaciones a la barra d'adreces i veure el llistat d'estacions. És el primer moment del curs en què la URL significa alguna cosa.
- Per què els proveïdors van fora de l'enrutador
L'ordre StrictMode > LimitError > ProveidorTema > ProveidorUsuari > RouterProvider no és arbitrari. Cada capa n'embolcalla la següent per una raó concreta:
flowchart TD
SM["StrictMode<br/><i>comprovacions de desenvolupament</i>"] --> LE["LimitError<br/><i>captura fallades de TOT,<br/>incloent l'enrutador</i>"]
LE --> PT["ProveidorTema<br/><i>sobreviu als canvis de ruta</i>"]
PT --> PU["ProveidorUsuari<br/><i>la sessió NO es perd en navegar</i>"]
PU --> RP["RouterProvider<br/><i>decideix quina pantalla pintar</i>"]
RP --> PAG["La pàgina activa<br/><i>canvia amb la URL</i>"]
style RP fill:#e0f2fe
style PAG fill:#dcfce7
L'argument clau: el que hi ha per sobre de l'enrutador no es desmunta en navegar. Si ProveidorUsuari estigués dins d'una ruta, canviar de / a /estaciones desmuntaria el proveïdor, el seu useState es reiniciaria i l'usuari perdria la sessió a cada clic del menú. El mateix amb el tema: passaries de fosc a clar cada vegada que canviessis de pantalla.
En forma de taula, el criteri per decidir on col·locar cada proveïdor:
| Situació | On va |
|---|---|
| L'estat ha de sobreviure tota la vida de l'aplicació (sessió, tema) | Fora de l'enrutador, a main.jsx |
| L'estat és del marc visual i el consumeixen diverses pantalles (avisos, reserves) | A la ruta arrel (Disseny), 06-03 |
| L'estat només interessa a una pantalla (terme de cerca del catàleg) | Dins d'aquesta pàgina |
I el LimitError global per sobre de tot té un motiu addicional: si el mateix enrutador llancés un error —una ruta mal configurada, un element que no és un element—, algú ho ha de capturar. A 06-03 veuràs que a més existeix errorElement, que actua dins de l'enrutador i per branca de ruta; els dos mecanismes conviuen i es reparteixen la feina.
- Enllaços:
<Link> enfront de <a href>
<Link> enfront de <a href>Moment de saldar el deute del mòdul 2. Recordem la Capcalera tal com porta escrita des de llavors:
// src/components/Capcalera.jsx — la versió amb el deute
<nav>
<a href="#cataleg">Catàleg</a>
<a href="#estacions">Estacions</a>
<a href="#reserves">Les meves reserves</a>
</nav>La temptació és substituir #cataleg per /:
I és un error, encara que «funcioni». Compara el que passa amb cadascun:
<a href="/estaciones"> |
<Link to="/estaciones"> |
|
|---|---|---|
| Quin HTML produeix? | <a href="/estaciones"> |
<a href="/estaciones"> (el mateix!) |
| En fer clic | El navegador descarta l'aplicació i demana la pàgina al servidor | Intercepta el clic, pushState i repinta |
| Recàrrega completa | Sí | No |
| Estat de React | Es perd tot: sessió, tema, reserves, avisos | Intacte |
| Temps | Centenars de mil·lisegons, amb pantalla en blanc | Instantani |
| Clic amb Ctrl / botó central | Obre en pestanya nova ✅ | Obre en pestanya nova ✅ |
| Cercadors i lectors de pantalla | El veuen com un enllaç ✅ | El veuen com un enllaç ✅ |
Aquell detall de les dues últimes files és important i sol sorprendre: <Link> renderitza un <a> real amb el seu href. No és un <div> amb un onClick, error habitual de qui es fabrica els seus propis enllaços. Per això conserva tot el bo d'un enllaç HTML —obrir en pestanya nova, copiar adreça, indexació, navegació per teclat, anunci correcte en un lector de pantalla, com exigia 03-06— i només canvia el comportament del clic simple, que intercepta amb preventDefault().
La regla és senzilla:
- Navegació dins de l'aplicació →
<Link to="…">. - Enllaç a un altre lloc web, a un PDF o a
mailto:→<a href="…">de tota la vida. React Router no l'ha d'interceptar.
// Correcte: intern amb Link, extern amb a
<Link to="/estaciones">Estacions</Link>
<a href="https://www.ciclourbano.test/ayuda" target="_blank" rel="noreferrer">
Centre d'ajuda
</a>Un avís que estalvia hores de depuració: si en algun moment veus que en prémer un enllaç es perd la sessió o parpelleja tota la pantalla, gairebé segur que has deixat un <a href> intern sense convertir. És el fallo més repetit en enrutar una aplicació existent.
<NavLink> i la classe activa: la nav de Capcalera
<NavLink> i la classe activa: la nav de CapcaleraPer a un menú de navegació cal alguna cosa més: marcar visualment quina és la pantalla actual. <NavLink> és un <Link> que sap si la seva destinació coincideix amb la URL activa.
// src/components/Capcalera.jsx — el deute del mòdul 2, saldat
import { NavLink } from 'react-router';
import MenuUsuari from './MenuUsuari.jsx';
import estils from './Capcalera.module.css';
function Capcalera() {
// Funció que decideix la classe segons l'estat de l'enllaç
const classesEnllac = ({ isActive }) =>
isActive ? `${estils.enllac} ${estils.actiu}` : estils.enllac;
return (
<header className={estils.capcalera}>
<NavLink to="/" className={estils.marca}>
CicloUrbano
</NavLink>
<nav aria-label="Navegació principal">
<NavLink to="/" end className={classesEnllac}>
Catàleg
</NavLink>
<NavLink to="/estaciones" className={classesEnllac}>
Estacions
</NavLink>
<NavLink to="/reservas" className={classesEnllac}>
Les meves reserves
</NavLink>
</nav>
<MenuUsuari />
</header>
);
}
export default Capcalera;Tres coses a explicar:
className pot ser una funció. NavLink la crida amb un objecte { isActive, isPending, isTransitioning } i fa servir el que retorni. És l'única prop de tot el projecte que es comporta així, i val la pena recordar per què: el component no sap quins noms de classe fa servir el teu CSS Module, així que et deixa decidir a tu. El mateix val per a style i per a children:
{/* children com a funció: útil per afegir una icona només a l'actiu */}
<NavLink to="/reservas">
{({ isActive }) => (
<>
{isActive && <span aria-hidden="true">▸ </span>}
Les meves reserves
</>
)}
</NavLink>La prop end no és opcional a l'enllaç del catàleg. Per defecte, un NavLink es considera actiu si la URL comença per la seva destinació. Com que totes les rutes comencen per /, l'enllaç «Catàleg» apareixeria marcat sempre, fins i tot estant a /estaciones. end exigeix coincidència exacta i resol el problema:
| URL actual | <NavLink to="/"> |
<NavLink to="/" end> |
<NavLink to="/estaciones"> |
|---|---|---|---|
/ |
actiu | actiu | inactiu |
/estaciones |
actiu ❌ | inactiu ✅ | actiu |
/estaciones/est-02 |
actiu ❌ | inactiu ✅ | actiu (desitjable) |
Fixa't en l'última fila: per a l'enllaç «Estacions» sí que volem el comportament per defecte, perquè estant al detall d'una estació té sentit que la secció continuï marcada. Per això end va només al primer.
Accessibilitat. Marcar l'enllaç actiu només amb color no n'hi ha prou (03-06). NavLink afegeix automàticament aria-current="page" a l'element actiu, que un lector de pantalla anuncia com «pàgina actual». Aprofita aquest atribut també al CSS, per no dependre d'una classe:
/* src/components/Capcalera.module.css */
.enllac {
color: var(--color-text);
text-decoration: none;
padding: calc(var(--espai) / 2) var(--espai);
border-radius: var(--radi);
}
.enllac:hover {
background: var(--color-vora);
}
.actiu,
.enllac[aria-current='page'] {
color: var(--color-marca);
font-weight: 700;
/* A més del color, un indicador no cromàtic */
box-shadow: inset 0 -2px 0 var(--color-marca);
}
- Segments dinàmics i
useParams
useParamsLa ruta /bicicletas/:bicicletaId té un segment dinàmic: els dos punts indiquen que aquest tros és variable i el seu valor s'ha de capturar.
| URL | Coincideix amb /bicicletas/:bicicletaId? |
useParams() retorna |
|---|---|---|
/bicicletas/bici-003 |
Sí | { bicicletaId: 'bici-003' } |
/bicicletas/42 |
Sí | { bicicletaId: '42' } |
/bicicletas |
No (falta el segment) | — |
/bicicletas/bici-003/piezas |
No (sobra un segment) | — |
Així es llegeix a la pàgina:
// src/pagines/PaginaFitxaBicicleta.jsx
import { useParams, Link } from 'react-router';
import { bicicletes, estacions } from '../dades/domini.js';
import EtiquetaEstat from '../components/EtiquetaEstat.jsx';
import PaginaNoTrobada from './PaginaNoTrobada.jsx';
function PaginaFitxaBicicleta() {
const { bicicletaId } = useParams();
const bicicleta = bicicletes.find((bici) => bici.id === bicicletaId);
// El paràmetre pot ser qualsevol cosa: la URL l'escriu l'usuari
if (!bicicleta) {
return <PaginaNoTrobada recurs="bicicleta" identificador={bicicletaId} />;
}
const estacio = estacions.find((est) => est.id === bicicleta.estacioId);
return (
<article>
<h2>{bicicleta.model}</h2>
<EtiquetaEstat estat={bicicleta.estat} />
<dl>
<dt>Tipus</dt>
<dd>{bicicleta.tipus}</dd>
<dt>Estació</dt>
<dd>
<Link to={`/estaciones/${bicicleta.estacioId}`}>
{estacio ? estacio.nom : 'Sense assignar'}
</Link>
</dd>
<dt>Preu per hora</dt>
<dd>{bicicleta.preuHora.toFixed(2)} €</dd>
</dl>
<Link to="/">Tornar al catàleg</Link>
</article>
);
}
export default PaginaFitxaBicicleta;I l'enllaç que hi porta, des de TargetaBicicleta:
// src/components/TargetaBicicleta.jsx — fragment
<h3>
<Link to={`/bicicletas/${bicicleta.id}`}>{bicicleta.model}</Link>
</h3>La conversió de tipus és responsabilitat teva. Aquest és el punt que més fallades silencioses provoca:
Tots els paràmetres de ruta són cadenes de text. Sempre. La URL és text; React Router no pot saber si
42és un número, un codi postal o un identificador.
A CicloUrbano no molesta, perquè els identificadors ja són cadenes (bici-003). Però si la teva API fes servir identificadors numèrics, això fallaria de manera desconcertant:
const { bicicletaId } = useParams(); // '3', una CADENA
bicicletes.find((b) => b.id === bicicletaId); // ❌ 3 === '3' és false → undefined
bicicletes.find((b) => b.id == bicicletaId); // ⚠️ funciona, però == és mala idea
bicicletes.find((b) => b.id === Number(bicicletaId)); // ✅ conversió explícitaConverteix-ho com més aviat millor i en un sol lloc, i valida el resultat:
const { bicicletaId } = useParams();
const idNumeric = Number(bicicletaId);
if (!Number.isInteger(idNumeric) || idNumeric <= 0) {
return <PaginaNoTrobada recurs="bicicleta" identificador={bicicletaId} />;
}
- Què fer quan l'identificador no existeix
Qualsevol pot escriure /bicicletas/bici-999 a la barra d'adreces, o arribar des d'un enllaç antic a una bicicleta donada de baixa. Tens tres estratègies, i triar bé importa:
| Estratègia | Com | Quan convé |
|---|---|---|
| Pintar el «no trobat» al seu lloc | if (!bicicleta) return <PaginaNoTrobada … /> |
Recomanada. La URL es conserva, l'usuari pot corregir-la i l'enllaç continua sent compartible per depurar |
| Redirigir al llistat | <Navigate to="/" replace /> |
Quan el detall no té sentit sense context i el llistat és una alternativa útil. Es veu a 06-04 |
| Llançar i deixar que ho capturi l'enrutador | throw new Response('No trobada', { status: 404 }) + errorElement |
Aplicacions grans amb gestió d'errors centralitzada. Es veu a 06-03 |
Amplia PaginaNoTrobada perquè serveixi als dos usos —URL inexistent i recurs inexistent— amb un missatge afinat:
// src/pagines/PaginaNoTrobada.jsx
import { Link, useLocation } from 'react-router';
import Avis from '../components/Avis.jsx';
/**
* Pantalla de «no trobat» de CicloUrbano.
* Props:
* - recurs (cadena, opcional): 'bicicleta', 'estació'… Si falta, és una URL desconeguda
* - identificador (cadena, opcional): l'id que no s'ha trobat
*/
function PaginaNoTrobada({ recurs, identificador }) {
const { pathname } = useLocation();
const titol = recurs
? `No existeix cap ${recurs} amb l'identificador «${identificador}»`
: 'Aquesta pàgina no existeix';
return (
<section>
<Avis to="error" titol={titol}>
<p>
{recurs
? 'Pot ser que s\'hagi donat de baixa o que l\'enllaç estigui desactualitzat.'
: `L'adreça ${pathname} no correspon a cap pantalla de CicloUrbano.`}
</p>
</Avis>
<p>
<Link to="/">Anar al catàleg</Link> · <Link to="/estaciones">Veure estacions</Link>
</p>
</section>
);
}
export default PaginaNoTrobada;Una precisió honesta que convé conèixer: aquesta pantalla no retorna un codi HTTP 404. El servidor ha lliurat l'index.html amb estat 200 i el 404 és només visual. Perquè un cercador rebi el codi correcte cal renderitzat en servidor (10-01). En una aplicació interna com el panell de CicloUrbano no és un problema; en una web pública, sí.
- Paràmetres de consulta amb
useSearchParams
useSearchParamsTorna a PaginaCataleg. El filtre per tipus viu avui en un useState, i això té el mateix defecte que denunciàvem a 06-01: si un operari filtra per electrica i passa l'adreça a un company, aquest veu el catàleg sense filtrar. El filtre és informació sobre el que l'usuari està mirant: pertany a la URL.
useSearchParams és als paràmetres de consulta el que useState és a l'estat local, amb una signatura deliberadament semblant:
Amb la diferència que parametres és un objecte URLSearchParams estàndard del navegador, no un objecte pla. Els seus mètodes:
| Mètode | Què fa |
|---|---|
parametres.get('tipo') |
Retorna el valor, o null si no hi és |
parametres.getAll('tipo') |
Tots els valors, si la clau es repeteix |
parametres.has('tipo') |
true / false |
parametres.set('tipo', 'carga') |
Fixa el valor (sobre una còpia, vegeu més avall) |
parametres.delete('tipo') |
Elimina la clau |
parametres.toString() |
"tipo=carga&orden=precio" |
Així queda el catàleg:
// src/pagines/PaginaCataleg.jsx — amb el filtre a la URL
import { useState } from 'react';
import { useSearchParams } from 'react-router';
import CercadorBicicletes from '../components/CercadorBicicletes.jsx';
import SelectorTipus from '../components/SelectorTipus.jsx';
import LlistaBicicletes from '../components/LlistaBicicletes.jsx';
import { bicicletes, estacions } from '../dades/domini.js';
function PaginaCataleg() {
const [terme, setTerme] = useState('');
const [parametres, establirParametres] = useSearchParams();
// La URL mana: si no hi ha ?tipo=, el filtre és 'todos'
const tipusTriat = parametres.get('tipo') ?? 'todos';
function gestionarCanviDeTipus(nouTipus) {
// Es parteix SEMPRE dels paràmetres actuals per no perdre la resta
const seguents = new URLSearchParams(parametres);
if (nouTipus === 'todos') {
seguents.delete('tipo'); // sense filtre, URL neta: '/' i no '/?tipo=todos'
} else {
seguents.set('tipo', nouTipus);
}
// replace: canviar de filtre no hauria d'omplir l'historial d'entrades
establirParametres(seguents, { replace: true });
}
const visibles = bicicletes
.filter((bici) => tipusTriat === 'todos' || bici.tipus === tipusTriat)
.filter((bici) => bici.model.toLowerCase().includes(terme.toLowerCase()));
return (
<section>
<h2>Catàleg de bicicletes</h2>
<CercadorBicicletes terme={terme} alCanviarTerme={setTerme} />
<SelectorTipus tipusTriat={tipusTriat} alCanviarTipus={gestionarCanviDeTipus} />
{visibles.length === 0 ? (
<AvisSenseResultats terme={terme} tipus={tipusTriat} />
) : (
<LlistaBicicletes bicicletes={visibles} estacions={estacions} />
)}
</section>
);
}
export default PaginaCataleg;L'essencial d'aquest codi:
SelectorTipusno s'ha tocat. Continua sent el component controlat de 03-04, amb els seus propstipusTriatialCanviarTipus. L'única cosa que ha canviat és d'on surt el valor i on va el canvi. Això és exactament el que buscàvem en mantenir els components desacoblats de l'enrutador (apartat 3).- Es copia abans de modificar:
new URLSearchParams(parametres). Mutar l'objecte que retorna el hook no repinta res i pot provocar incoherències, per la mateixa raó d'immutabilitat de 05-01. ?tipo=todoss'elimina en comptes d'escriure's. Un valor per defecte no ha d'embrutar la URL;/i/?tipo=todosmostrarien el mateix i són dues adreces diferents, dolent per compartir i per a l'analítica.{ replace: true }evita que cada clic al filtre afegeixi una entrada a l'historial. Si un usuari prova cinc tipus, no voldrà prémer «enrere» cinc vegades per sortir del catàleg. És un judici de disseny: per a una paginació sol preferir-se deixar rastre.- El terme de cerca continua en
useState. Decisió deliberada: es tecleja lletra a lletra i portar-lo a la URL generaria desenes d'entrades o exigiria combinar-lo amb l'useDebouncede 05-06. Quan el cercador hagi de ser compartible, el patró seràuseSearchParams+useDebounce.
Què es guanya exactament en convertir estat de React en estat de la URL:
Abans (useState) |
Després (useSearchParams) |
|---|---|
/ sempre, filtri el que filtri |
/?tipo=electrica |
| Compartir l'enllaç perd el filtre | El company veu exactament el mateix |
| F5 torna al catàleg sense filtrar | F5 conserva el filtre |
| «Enrere» surt de l'aplicació | «Enrere» torna al filtre anterior (si no fas servir replace) |
| L'analítica no distingeix filtres | Es pot mesurar quin tipus es consulta més |
| L'estat inicial cal inventar-lo | El dicta la URL |
I la contrapartida honesta: la URL és pública i compartible, així que hi ha d'anar només allò que no importi que es vegi i es desi. Filtres, ordenacions, número de pàgina, pestanya activa: sí. Dades personals, contingut d'un formulari o qualsevol secret: mai.
- La ruta comodí i la pàgina de no trobat
L'última entrada del mapa és:
L'asterisc és la ruta comodí (splat route): coincideix amb qualsevol URL que cap altra ruta hagi reclamat. El seu paper és que un error tipogràfic produeixi una pantalla útil en lloc d'un buit. Sense ella, /estacionez renderitzaria absolutament res: pantalla en blanc, sense cap error a la consola. És un fallo desconcertant i molt comú oblidar aquesta línia.
El valor capturat pel comodí es llegeix amb la clau '*', útil si vols mostrar-lo o registrar-lo:
Un comodí també serveix en un punt intermedi, no només a l'arrel. { path: '/documentacion/*' } captura qualsevol profunditat sota aquest prefix, patró habitual en integrar contingut extern. A CicloUrbano no cal.
- Com tria React Router la millor coincidència
Pregunta natural: si la URL és /reservas/nueva, per què no coincideix amb /reservas o amb *? La resposta és una de les millors decisions de disseny de React Router des de la v6:
React Router puntua totes les rutes i tria la més específica, sense importar en quin ordre les hagis escrit a l'array.
El criteri de puntuació, simplificat:
| Tipus de segment | Puntuació | Exemple |
|---|---|---|
| Segment literal | Alta | estaciones |
| Segment dinàmic | Mitjana | :estacionId |
Comodí * |
Mínima | * |
Aplicat a /estaciones/est-02, amb el mapa complet:
| Ruta candidata | Coincideix? | Puntuació | Resultat |
|---|---|---|---|
/estaciones/:estacionId |
Sí | literal + dinàmic → alta | ✅ Guanya |
/estaciones |
No, sobra un segment | — | Descartada |
* |
Sí | mínima | Perdedora |
Compara-ho amb React Router v5 i anteriors, on <Switch> prenia la primera ruta que coincidís en ordre d'escriptura. Allò obligava a ordenar l'array de més específic a més general, a fer servir exact per tot arreu i produïa errors molt difícils de veure: una ruta col·locada dues línies més amunt del compte se «menjava» totes les altres. Avui pots escriure el mapa en l'ordre que et resulti llegible.
Dos matisos que convé retenir:
- La coincidència ha de ser completa.
/estacionesno coincideix amb la URL/estaciones/est-02a menys que tingui rutes filles que absorbeixin la resta (06-03) o acabi en*. endaNavLinkés una altra cosa. Governa només la classe activa de l'enllaç, no quina ruta es pinta. És normal confondre'ls al principi.
flowchart TD
URL["URL: /estaciones/est-02"] --> CAND{"Rutes candidates"}
CAND --> R1["/estaciones/:estacionId<br/>literal + dinàmic"]
CAND --> R2["/estaciones<br/>❌ no cobreix tota la URL"]
CAND --> R3["*<br/>puntuació mínima"]
R1 --> GANA["✅ Es renderitza<br/>PaginaDetallEstacio<br/>params: { estacionId: 'est-02' }"]
style GANA fill:#dcfce7
style R2 fill:#fecaca
Errors Comuns i Consells
Passar el component en lloc de l'element. { path: '/', element: PaginaCataleg } en lloc de element: <PaginaCataleg />. No llança un error clar: simplement no es pinta res. Recorda que element rep JSX ja creat.
Deixar <a href> interns sense convertir. El símptoma és inconfusible: en prémer un enllaç, la pantalla parpelleja i es perden la sessió, el tema i les reserves. Busca href="/ al teu projecte i converteix tot el que sigui intern a <Link>.
Oblidar end al NavLink de l'inici. L'enllaç «Catàleg» queda marcat com a actiu a totes les pantalles. El mateix passa amb qualsevol NavLink la destinació del qual sigui prefix d'altres rutes.
Cridar createBrowserRouter dins d'un component. Cada render crearia un enrutador nou, i l'aplicació tornaria a la ruta inicial tan bon punt alguna cosa repintés. El mapa es crea una vegada, en l'àmbit del mòdul.
Mutar l'objecte de useSearchParams. parametres.set('tipo', 'carga') sobre l'objecte que retorna el hook no provoca cap repintat. Copia sempre: new URLSearchParams(parametres).
Posar un ? de més en construir la URL. establirParametres('?tipo=carga') produeix ??tipo=carga. Passa un URLSearchParams o un objecte pla i deixa que React Router posi el separador.
Esperar un número a useParams. Tot és cadena. Converteix i valida, en un sol lloc i com més aviat millor.
Oblidar la ruta *. Qualsevol error tipogràfic produeix una pantalla en blanc silenciosa, sense error a la consola. És dels primers fallos que un usuari reporta.
Consell: anomena els paràmetres igual a la ruta i al codi. path: '/bicicletas/:bicicletaId' i const { bicicletaId } = useParams(). Si la ruta diu :id i desestructures bicicletaId, obtindràs undefined sense cap avís.
Consell: mantén una única font de veritat per dada. Si el tipus triat viu a la URL, no el duplicis en un useState «per tenir-lo a mà». Duplicar-lo garanteix que tard o d'hora es desincronitzaran, exactament com advertia 05-01 sobre l'estat derivat.
Consell: comprova la recàrrega a cada ruta nova. Navegar amb enllaços sempre funciona; recarregar és el que descobreix els problemes de configuració del servidor. Amb Vite en desenvolupament no veuràs el fallo, però convé tenir present el que hem vist a 06-01 sobre la reescriptura a index.html.
Exercicis
Exercici 1: la ruta de detall d'estació
Afegeix a CicloUrbano la pantalla de detall d'estació, /estaciones/:estacionId. Ha de:
- Llegir el paràmetre i buscar l'estació a
estacions. - Mostrar nom, barri i places.
- Llistar les bicicletes l'
estacioIdde les quals hi coincideixi, cadascuna enllaçant a la seva fitxa. - Mostrar el «no trobat» si l'identificador no existeix.
- Incloure un enllaç de tornada a
/estaciones.
Afegeix a més l'enllaç des de TargetaEstacio per arribar-hi.
Exercici 2: ordenar el llistat des de la URL
A PaginaEstacions, afegeix un desplegable que permeti ordenar les estacions per nom o per places, desant la tria en un paràmetre de consulta orden. Requisits:
- La URL ha de quedar
/estaciones?orden=plazas. - El valor per defecte (
nombre) no ha d'aparèixer a la URL. - No s'han de perdre altres paràmetres de consulta que pogués haver-hi.
- Un valor invàlid escrit a mà (
?orden=inventado) no ha de trencar la pantalla.
Exercici 3: detectar els fallos
Aquest mapa de rutes i aquesta capçalera tenen cinc errors. Troba'ls i corregeix-los.
// src/rutes.jsx
import { createBrowserRouter } from 'react-router';
import PaginaCataleg from './pagines/PaginaCataleg.jsx';
import PaginaFitxaBicicleta from './pagines/PaginaFitxaBicicleta.jsx';
import PaginaEstacions from './pagines/PaginaEstacions.jsx';
function crearRouter() {
return createBrowserRouter([
{ path: '/', element: PaginaCataleg },
{ path: '/bicicletas/:id', element: <PaginaFitxaBicicleta /> },
{ path: '/estaciones', element: <PaginaEstacions /> }
]);
}
export default crearRouter;// src/components/Capcalera.jsx
import { NavLink } from 'react-router';
function Capcalera() {
return (
<nav>
<NavLink to="/" className={({ isActive }) => (isActive ? 'actiu' : '')}>
Catàleg
</NavLink>
<a href="/estaciones">Estacions</a>
</nav>
);
}// src/pagines/PaginaFitxaBicicleta.jsx — fragment
const { bicicletaId } = useParams();
const bicicleta = bicicletes.find((bici) => bici.id === bicicletaId);
return <h2>{bicicleta.model}</h2>;Solucions
Solució 1
// src/pagines/PaginaDetallEstacio.jsx
import { useParams, Link } from 'react-router';
import { estacions, bicicletes } from '../dades/domini.js';
import EtiquetaEstat from '../components/EtiquetaEstat.jsx';
import PaginaNoTrobada from './PaginaNoTrobada.jsx';
function PaginaDetallEstacio() {
const { estacionId } = useParams();
const estacio = estacions.find((est) => est.id === estacionId);
if (!estacio) {
return <PaginaNoTrobada recurs="estació" identificador={estacionId} />;
}
const flota = bicicletes.filter((bici) => bici.estacioId === estacio.id);
return (
<article>
<h2>{estacio.nom}</h2>
<p>
Barri de {estacio.barri} · {estacio.places} places ·{' '}
{flota.length} bicicletes assignades
</p>
<h3>Flota de l'estació</h3>
{flota.length === 0 ? (
<p>No hi ha bicicletes assignades a aquesta estació.</p>
) : (
<ul>
{flota.map((bici) => (
<li key={bici.id}>
<Link to={`/bicicletas/${bici.id}`}>{bici.model}</Link>{' '}
<EtiquetaEstat estat={bici.estat} />
</li>
))}
</ul>
)}
<Link to="/estaciones">Tornar a les estacions</Link>
</article>
);
}
export default PaginaDetallEstacio;// src/components/TargetaEstacio.jsx — fragment amb l'enllaç afegit
<h3>
<Link to={`/estaciones/${estacio.id}`}>{estacio.nom}</Link>
</h3>Amb la ruta ja present a rutes.jsx, no hi ha res més a registrar. I observa que /estaciones i /estaciones/est-02 conviuen sense problema gràcies a la puntuació de l'apartat 13: l'ordre a l'array és irrellevant.
Solució 2
// src/pagines/PaginaEstacions.jsx
import { useSearchParams } from 'react-router';
import TargetaEstacio from '../components/TargetaEstacio.jsx';
import { estacions, bicicletes } from '../dades/domini.js';
const ORDRES_VALIDS = ['nombre', 'plazas'];
const ORDRE_PER_DEFECTE = 'nombre';
function PaginaEstacions() {
const [parametres, establirParametres] = useSearchParams();
// Validació: un valor inventat a la URL cau al valor per defecte
const ordreBrut = parametres.get('orden');
const ordre = ORDRES_VALIDS.includes(ordreBrut) ? ordreBrut : ORDRE_PER_DEFECTE;
function gestionarCanviDeOrdre(esdeveniment) {
const nou = esdeveniment.target.value;
const seguents = new URLSearchParams(parametres); // còpia: conserva la resta
if (nou === ORDRE_PER_DEFECTE) {
seguents.delete('orden'); // el valor per defecte no embruta la URL
} else {
seguents.set('orden', nou);
}
establirParametres(seguents, { replace: true });
}
// Còpia abans d'ordenar: sort() muta l'array original (05-01)
const ordenades = [...estacions].sort((a, b) =>
ordre === 'plazas' ? b.places - a.places : a.nom.localeCompare(b.nom)
);
return (
<section>
<h2>Estacions</h2>
<p>
<label htmlFor="orden-estaciones">Ordenar per</label>{' '}
<select id="orden-estaciones" value={ordre} onChange={gestionarCanviDeOrdre}>
<option value="nom">Nom (A-Z)</option>
<option value="places">Places (més primer)</option>
</select>
</p>
<ul>
{ordenades.map((estacio) => (
<li key={estacio.id}>
<TargetaEstacio
estacio={estacio}
bicicletesEnEstacio={bicicletes.filter((b) => b.estacioId === estacio.id)}
/>
</li>
))}
</ul>
</section>
);
}
export default PaginaEstacions;Els quatre requisits queden coberts per: la llista blanca ORDRES_VALIDS (validació), el delete quan el valor és el de per defecte (URL neta), la còpia new URLSearchParams(parametres) (no perdre altres paràmetres) i el [...estacions] abans de sort (no mutar les dades originals). El <select> és un component controlat de 03-04, amb la particularitat que el seu valor surt de la URL, no d'un useState.
Solució 3
Els cinc errors:
element: PaginaCatalegpassa el component, no l'element. Ha de serelement: <PaginaCataleg />. Símptoma: pantalla en blanc sense error clar.createBrowserRouterdins d'una funció que s'exporta comcrearRouter. Simain.jsxla crida a cada render, es crea un enrutador nou cada vegada. S'ha de crear una sola vegada en l'àmbit del mòdul i exportar-se amb nom:export const router = createBrowserRouter([...]);.- Falta la ruta comodí
{ path: '*', element: <PaginaNoTrobada /> }. Qualsevol URL desconeguda deixa la pantalla buida en silenci. - Desajust de noms: la ruta declara
:idperò la pàgina desestructurabicicletaId, que seràundefined. Han de coincidir; el correcte segons el mapa del mòdul és:bicicletaIdals dos llocs. - A
Capcalera,<a href="/estaciones">provoca una recàrrega completa i perd tot l'estat de React. Ha de ser<NavLink to="/estaciones">.
I un sisè fallo, latent, que convé assenyalar: a PaginaFitxaBicicleta no es comprova si bicicleta existeix, així que /bicicletas/bici-999 rebentarà amb «Cannot read properties of undefined (reading 'model')». Afegeix la guarda de l'apartat 10. Nota, a més, que aquest error sí que el capturaria el LimitError global de 04-05, però tombant l'aplicació sencera per una cosa que és simplement una URL mal escrita: gestionar-lo al seu lloc és molt millor experiència.
Conclusió
CicloUrbano ja està enrutada. Has instal·lat react-router —el paquet de la v7, sabent que react-router-dom és el que veuràs en projectes de la v6—, has creat src/rutes.jsx amb el mapa complet com a array d'objectes i has muntat <RouterProvider router={router} /> a main.jsx conservant l'ordre StrictMode > LimitError > ProveidorTema > ProveidorUsuari, perquè el que hi ha per sobre de l'enrutador no es desmunta en navegar i per això la sessió i el tema sobreviuen als canvis de pantalla. El projecte té una carpeta src/pagines/ amb una pantalla per ruta i una regla clara: les pàgines poden dependre de l'enrutador; els components reutilitzables reben les seves dades per props i continuen sent provables per separat.
Pel camí has saldat el deute que arrossegava la Capcalera des del mòdul 2: els tres <a href="#…"> són ara <NavLink> amb classe activa, aria-current="page" gratuïtament i la prop end a l'enllaç del catàleg perquè no aparegui marcat a totes les pantalles. Saps que <Link> produeix un <a> real —amb tot el bo d'un enllaç HTML— i només intercepta el clic simple, mentre que un <a href> intern destrueix l'aplicació i s'emporta per davant sessió, tema i reserves. Has llegit l'identificador de la fitxa amb useParams, recordant que tot paràmetre de ruta és una cadena i que la conversió i la validació són teves, i has decidit què fer quan el recurs no existeix. I has convertit el filtre del SelectorTipus d'estat de React a estat de la URL amb useSearchParams, guanyant enllaços compartibles, recàrrega que conserva el filtre i analítica útil, sense tocar ni una línia del component controlat. Tanquen el quadre la ruta comodí * —l'absència de la qual produeix pantalles en blanc silencioses— i el sistema de puntuació que tria la coincidència més específica sense dependre de l'ordre de l'array.
Queda un problema visible: les pantalles es pinten soles, sense capçalera ni peu, perquè Disseny s'ha quedat fora del mapa. Reintroduir-lo com un component que embolcalla cada pàgina funcionaria, però tornaria a muntar la capçalera a cada navegació i no permetria compartir dades entre marc i pantalla. La solució correcta és fer de Disseny la ruta pare de totes les altres i obrir-hi un forat on pintar la filla activa; amb la mateixa idea construiràs les pestanyes flota i incidencias dins del detall d'estació, les molles de pa i la gestió d'errors per branca. La propera lliçó és Rutes Imbricades.
Curs de React
Mòdul 1: Introducció a React
- Què és React?
- Configuració de l'Entorn de Desenvolupament
- Hola Món amb React
- JSX: Extensió de Sintaxi de JavaScript
- Com Renderitza React: Virtual DOM i Reconciliació
Mòdul 2: Components de React
- Entendre els Components
- Components Funcionals vs de Classe
- Props: Passar Dades als Components
- State: Gestió de l'Estat del Component
- Estils en els Components: CSS, Mòduls i Utilitats
Mòdul 3: Treballar amb Esdeveniments
- Gestió d'Esdeveniments a React
- Renderitzat Condicional
- Llistes i Claus
- Formularis i Components Controlats
- Validació de Formularis i Components No Controlats
- Accessibilitat en Components Interactius
Mòdul 4: Conceptes Avançats de Components
- Elevar l'Estat
- Composició vs Herència
- Mètodes del Cicle de Vida de React
- Hooks: Introducció i Ús Bàsic
- Límits d'Error: Capturar Fallades a la Interfície
Mòdul 5: Hooks de React
- Hook useState
- Hook useEffect
- Hook useRef i Accés al DOM
- Hook useContext
- Hook useReducer
- Hooks Personalitzats
Mòdul 6: Enrutament a React
- Introducció a React Router
- Configuració de React Router
- Rutes Imbricades
- Navegació Programàtica
- Rutes Protegides i Control d'Accés
Mòdul 7: Gestió de l'Estat
- Introducció a la Gestió de l'Estat
- API de Context
- Redux: Introducció i Configuració
- Redux: Accions i Reductors
- Redux: Connectar-lo a React
- Estat del Servidor: Peticions, Memòria Cau i Sincronització
Mòdul 8: Optimització del Rendiment
- Tècniques d'Optimització del Rendiment a React
- Memoïtzació amb React.memo
- Hooks useMemo i useCallback
- Divisió de Codi i Càrrega Mandrosa
- Mesurar el Rendiment amb React DevTools Profiler
Mòdul 9: Proves a React
- Introducció a les Proves
- Proves Unitàries amb Jest
- Proves de Components amb React Testing Library
- Proves de Codi Asíncron i Simulació d'APIs
- Proves d'Extrem a Extrem amb Cypress
Mòdul 10: Temes Avançats
- Renderitzat al Servidor (SSR) amb Next.js
- Generació de Llocs Estàtics (SSG) amb Next.js
- Suspense i React Server Components
- TypeScript amb React
- React Native: Creació d'Aplicacions Mòbils
