Lezione 5 – Routing

React + Vite + Node.js Lezione 05 / 10 ~30 min lettura

Navigazione lato client, URL parametrici, layout annidati e route protette — senza mai ricaricare la pagina.

// cosa costruiremo

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.

MPA — Multi-Page App
Click su link
Richiesta HTTP al server
Pagina si ricarica ↩
Nuovo HTML completo
SPA — Single-Page App
Click su Link
React Router intercetta
URL aggiornato (History API) ✓
Solo il componente cambia ✓

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 api — sotto il cofano

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-dom

React 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.

// schema route annidate con outlet
URL: /
<Layout>
  // navbar qui
  <Outlet>
    <Home /> ← index
  </Outlet>
</Layout>
URL: /note/3
<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;
// prop end su navlink

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;
// attenzione — tipo dei parametri URL

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

NomeTipoScopoRitorna / Props chiave
BrowserRouterComponenteContesto router — avvolge tutta l’app in main.jsx
RoutesComponenteContenitore delle route — matcha l’URL corrente
RouteComponenteDefinisce un mapping URL → componentepath, element, index
LinkComponenteNavigazione dichiarativa senza reloadto, replace, state
NavLinkComponenteCome Link ma con prop isActive per lo stileto, end, className (funzione)
OutletComponenteSegnaposto per route figlie nel layoutcontext (dati passabili alle figlie)
NavigateComponenteRedirect dichiarativo — renderizza e naviga subitoto, replace
useParamsHookLegge i parametri dinamici dall’URL (:id){ id, slug, ... } — sempre stringhe
useNavigateHookNavigazione programmatica in event handlerfunzione navigate(to, options)
useLocationHookLegge pathname, search, hash, state correnti{ pathname, search, hash, state }
useSearchParamsHookLegge e modifica i query string (?q=react)[searchParams, setSearchParams]

// lab — metti in pratica

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 →

📌 Riepilogo — Punti chiave
  • 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-dom e avvolgi l’app in <BrowserRouter> in main.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 prop isActive per 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>.

Lascia un commento