Navigazione lato client, URL parametrici, layout annidati e route protette — senza mai ricaricare la pagina.
Trasformeremo DevNotes da single-page in una vera SPA multi-route: una pagina Home con la lista note, una pagina di dettaglio nota con URL parametrico (/note/3), una pagina Impostazioni e un layout condiviso con la navbar. Aggiungeremo anche una route protetta per simulare l’autenticazione.
SPA vs MPA — il problema della navigazione
Nelle applicazioni web tradizionali (Multi-Page App), ogni clic su un link fa una richiesta HTTP al server, che risponde con un nuovo HTML completo. La pagina si ricarica completamente.
Nella SPA il browser carica una sola volta l’HTML shell + il bundle JS. Poi React Router usa la History API del browser (history.pushState) per cambiare l’URL nella barra degli indirizzi e React effettua il rendering del componente corretto — senza network request e senza flickering.
history.pushState(state, '', '/note/3') aggiorna l’URL visibile nella barra del browser senza ricaricare la pagina. React Router ascolta questi eventi e mappa l’URL al componente corrispondente. Se ricarichi manualmente la pagina su /note/3, il server deve servire lo stesso index.html — configurazione necessaria in deploy (vedi Lab 10).
Installazione di React Router v6
React Router non è incluso in React di default — è una libreria separata da installare:
# installa react-router-dom (la versione per browser)
npm install react-router-domReact Router ha due pacchetti: react-router (core) e react-router-dom (per browser, aggiunge componenti HTML come BrowserRouter). In un’app React normale installi e importi sempre da react-router-dom.
Struttura di base — BrowserRouter, Routes, Route
Il punto di ingresso è avvolgere l’intera applicazione in un BrowserRouter. Il posto corretto è main.jsx:
// src/main.jsx import React from 'react' import ReactDOM from 'react-dom/client' import { BrowserRouter } from 'react-router-dom' import App from './App.jsx' import './index.css' ReactDOM.createRoot(document.getElementById('root')).render( <React.StrictMode> <BrowserRouter> {/* ← avvolgi tutto l'app */} <App /> </BrowserRouter> </React.StrictMode> )
Ora in App.jsx puoi definire le route:
// src/App.jsx — versione con routing import { Routes, Route } from 'react-router-dom' import Layout from './components/Layout' import Home from './pages/Home' import NoteDetail from './pages/NoteDetail' import Impostazioni from './pages/Impostazioni' import NotFound from './pages/NotFound' function App() { return ( <Routes> {/* Layout è il wrapper con navbar — Outlet renderizza le figlie */} <Route path="/" element={<Layout />}> <Route index element={<Home />} /> <Route path="note/:id" element={<NoteDetail />} /> <Route path="impostazioni" element={<Impostazioni />} /> </Route> <Route path="*" element={<NotFound />} /> </Routes> ); }
Route annidate — come funzionano
Quando una Route ha figli, il suo componente element deve contenere un <Outlet />. Outlet è un segnaposto: React Router ci renderizza dentro la route figlia che matcha l’URL corrente.
// navbar qui
<Outlet> ←
<Home /> ← index
</Outlet>
</Layout>
// stessa navbar
<Outlet> ←
<NoteDetail id=”3″ />
</Outlet>
</Layout>
Il vantaggio è immediato: la navbar non si smonta e rimonta a ogni navigazione — persiste nell’albero React. Solo il contenuto dentro Outlet cambia.
Link e NavLink — navigazione dichiarativa
Mai usare <a href="..."> in una SPA React: causerebbe una ricarica completa della pagina. React Router fornisce <Link> e <NavLink>:
import { Link, NavLink } from 'react-router-dom' // Link — navigazione semplice <Link to="/">Home</Link> <Link to={`/note/${nota.id}`}>Apri nota</Link> // NavLink — come Link ma sa se è "attivo" // className riceve { isActive } e può restituire una stringa <NavLink to="/impostazioni" className={({ isActive }) => isActive ? 'text-cyan-400 border-b border-cyan-400' : 'text-slate-400' } > Impostazioni </NavLink>
NavLink è ideale per le navbar: isActive è true quando l’URL corrente matcha il to della voce. Puoi applicare stili diversi per l’elemento attivo senza gestire manualmente lo stato.
Il componente Layout con Outlet
Creiamo il layout condiviso dell’applicazione:
// src/components/Layout.jsx import { Outlet, NavLink } from 'react-router-dom' function Layout() { // funzione helper per le classi NavLink const navClasse = ({ isActive }) => isActive ? 'font-mono text-[12px] text-cyan-400 border-b border-cyan-400 pb-0.5' : 'font-mono text-[12px] text-[#6B7A99] hover:text-white transition-colors'; return ( <div className="max-w-5xl mx-auto px-4 py-8"> {/* ── Navbar */} <nav className="flex items-center justify-between mb-10 border-b border-border pb-5"> <NavLink to="/" className="font-serif text-xl font-black text-white"> 📝 DevNotes </NavLink> <div className="flex items-center gap-6"> <NavLink to="/" end className={navClasse}>note</NavLink> <NavLink to="/impostazioni" className={navClasse}>impostazioni</NavLink> </div> </nav> {/* ── Outlet: qui React Router monta la pagina corrente */} <main> <Outlet /> </main> </div> ); } export default Layout;
La prop end su <NavLink to="/" end> indica che il match deve essere esatto. Senza end, la voce “/” sarebbe sempre attiva perché ogni URL inizia con “/”.
Parametri URL con useParams
La route path="note/:id" definisce un segmento dinamico. I due punti indicano che :id può essere qualsiasi valore. Per leggerlo nel componente usi l’hook useParams:
// src/pages/NoteDetail.jsx import { useParams, Link } from 'react-router-dom' import { useContext } from 'react' import { NoteContext } from '../context/NoteContext' function NoteDetail() { // ① leggi i parametri dall'URL — { id: "3" } const { id } = useParams(); // ② cerca la nota nello state (context globale) const { note } = useContext(NoteContext); const nota = note.find(n => n.id === Number(id)); // id URL è sempre string! // ③ nota non trovata if (!nota) { return ( <div className="text-center py-20"> <p className="text-[#6B7A99] font-mono text-sm mb-4">Nota non trovata.</p> <Link to="/" className="font-mono text-sm text-cyan-400">← Torna alle note</Link> </div> ); } return ( <article className="max-w-2xl"> <Link to="/" className="font-mono text-[11px] text-[#6B7A99] hover:text-white mb-6 inline-block"> ← tutte le note </Link> <h1 className="font-serif text-3xl font-black text-white mb-4"> {nota.titolo} </h1> <p className="text-[#A8B8D0] leading-relaxed"> {nota.testo || 'Nessun contenuto.'} </p> </article> ); } export default NoteDetail;
I parametri letti da useParams sono sempre stringhe, anche se l’URL contiene un numero. Se gli ID nel tuo state sono numerici, devi convertire: Number(id) o parseInt(id, 10). Un confronto n.id === id tra numero e stringa restituisce sempre false.
Navigazione programmatica con useNavigate
A volte devi navigare in risposta a un evento che non è un click su un link: dopo il submit di un form, dopo un’operazione asincrona, dopo un logout. Per questo esiste useNavigate:
import { useNavigate } from 'react-router-dom' function NoteForm({ onAggiungi }) { const navigate = useNavigate(); async function handleSubmit(e) { e.preventDefault(); const nuovaNota = await onAggiungi(formData); // naviga alla pagina di dettaglio della nota appena creata navigate(`/note/${nuovaNota.id}`); } // navigate accetta anche un numero per navigare nella history: // navigate(-1) → torna indietro come il pulsante Back // navigate(1) → avanza // navigate('/home', { replace: true }) → sostituisce l'entry history }
Route protette — simulare l’autenticazione
Un pattern fondamentale è la route protetta: una route che mostra il contenuto solo agli utenti autenticati, e redirige al login altrimenti. In React Router v6 si implementa come componente wrapper:
// src/components/ProtectedRoute.jsx import { Navigate, Outlet } from 'react-router-dom' import { useContext } from 'react' import { AuthContext } from '../context/AuthContext' function ProtectedRoute() { const { isLoggedIn } = useContext(AuthContext); // se non autenticato → redirect alla pagina login if (!isLoggedIn) { return <Navigate to="/login" replace />; } // se autenticato → renderizza la route figlia return <Outlet />; } export default ProtectedRoute;
Usalo come genitore delle route che vuoi proteggere:
// in App.jsx <Routes> <Route path="/" element={<Layout />}> <Route index element={<Home />} /> {/* queste route richiedono autenticazione */} <Route element={<ProtectedRoute />}> <Route path="note/:id" element={<NoteDetail />} /> <Route path="impostazioni" element={<Impostazioni />} /> </Route> </Route> <Route path="login" element={<Login />} /> <Route path="*" element={<NotFound />} /> </Routes>
Riepilogo hook e componenti React Router v6
| Nome | Tipo | Scopo | Ritorna / Props chiave |
|---|---|---|---|
BrowserRouter | Componente | Contesto router — avvolge tutta l’app in main.jsx | — |
Routes | Componente | Contenitore delle route — matcha l’URL corrente | — |
Route | Componente | Definisce un mapping URL → componente | path, element, index |
Link | Componente | Navigazione dichiarativa senza reload | to, replace, state |
NavLink | Componente | Come Link ma con prop isActive per lo stile | to, end, className (funzione) |
Outlet | Componente | Segnaposto per route figlie nel layout | context (dati passabili alle figlie) |
Navigate | Componente | Redirect dichiarativo — renderizza e naviga subito | to, replace |
useParams | Hook | Legge i parametri dinamici dall’URL (:id) | { id, slug, ... } — sempre stringhe |
useNavigate | Hook | Navigazione programmatica in event handler | funzione navigate(to, options) |
useLocation | Hook | Legge pathname, search, hash, state correnti | { pathname, search, hash, state } |
useSearchParams | Hook | Legge e modifica i query string (?q=react) | [searchParams, setSearchParams] |
Lab 05 — Routing di DevNotes
Aggiungi react-router-dom, crea il Layout con navbar, la pagina di dettaglio nota con URL parametrico, la 404 e una route protetta simulata.
Apri lab-05-routing.md →- Le SPA usano la History API del browser per cambiare URL senza ricaricare la pagina — React Router è il gestore di routing standard per React.
- Installa con
npm install react-router-dome avvolgi l’app in<BrowserRouter>inmain.jsx. <Routes>e<Route path="..." element={...}>definiscono il mapping URL → componente.- Le route annidate con
<Outlet />permettono layout condivisi (navbar, footer) senza rimontare i componenti a ogni navigazione. <Link to="...">sostituisce<a href>.<NavLink>aggiunge la propisActiveper evidenziare la voce di menu attiva.useParams()legge i segmenti dinamici dell’URL (:id) — ricorda che il valore è sempre una stringa.useNavigate()permette la navigazione programmatica da event handler e funzioni async.- Le route protette si implementano con un componente wrapper che controlla l’auth e usa
<Navigate>o<Outlet>.