Guida Completa a React Router v6: Gestire la Navigazione nelle Single Page Application

Intermedio
JavaScript React

Impara a implementare un sistema di routing professionale in React. Dalle rotte base ai parametri dinamici, fino ai protected routes e ai data loaders.

Pubblicato
Tag
javascript frontend React SPA React Router

Introduzione al Routing in React

In un'applicazione web tradizionale, ogni volta che un utente clicca su un link, il browser invia una richiesta al server, che risponde restituendo un intero nuovo documento HTML. Questo processo causa un refresh della pagina, interrompendo l'esperienza dell'utente e aumentando i tempi di caricamento.

Le Single Page Application (SPA), costruite con framework come React, cambiano radicalmente questo paradigma. In una SPA, l'intera applicazione viene caricata una sola volta. Il "cambio di pagina" non avviene tramite l'invio di una nuova richiesta HTML, ma attraverso la manipolazione del DOM lato client. Qui entra in gioco React Router, la libreria standard de facto per gestire la navigazione in React.

React Router permette di sincronizzare l'interfaccia utente con l'URL del browser. Questo significa che l'utente può ancora usare i tasti "Avanti" e "Indietro" del browser, può salvare i link nei preferiti e può condividere URL specifici, nonostante l'applicazione non ricarichi mai l'intera pagina.

Configurazione Iniziale e Concetti Base

Per iniziare a utilizzare React Router, è necessario installare il pacchetto principale tramite npm o yarn:

npm install react-router-dom

Il cuore di React Router v6 risiede in alcuni componenti fondamentali che definiscono come l'app deve comportarsi in base all'URL. Il componente principale è BrowserRouter, che avvolge l'intera applicazione per abilitare l'integrazione con l'API History del browser.

Struttura di base

Ecco come impostare una configurazione di routing elementare. In questo esempio, definiamo tre pagine: Home, About e Contatti.

import React from 'react';
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';

// Componenti Pagina
const Home = () => <h2>Benvenuti nella Home Page</h2>;
const About = () => <h2>Chi Siamo</h2>;
const Contact = () => <h2>Contattaci</h2>;

function App() {
  return (
    <BrowserRouter>
      <nav>
        <ul>
          <li><Link to="/">Home</Link></li>
          <li><Link to="/about">About</Link></li>
          <li><Link to="/contact">Contact</Link></li>
        </ul>
      </nav>

      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/contact" element={<Contact />} />
      </Routes>
    </BrowserRouter>
  );
}

export default App;

Spiegazione del codice:

  • BrowserRouter: Fornisce il contesto di routing a tutta l'app.
  • Routes: Agisce come un contenitore che esamina l'URL attuale e renderizza solo il primo Route che corrisponde al percorso.
  • Route: Definisce la relazione tra un percorso (path) e il componente da visualizzare (element).
  • Link: Sostituisce il tag <a>. A differenza del tag anchor classico, Link impedisce il refresh della pagina e aggiorna l'URL internamente.

Rotte Dinamiche e Parametri dell'URL

Nelle applicazioni reali, non possiamo creare una rotta statica per ogni singolo elemento (ad esempio, per ogni prodotto di un e-commerce o ogni profilo utente). Utilizziamo quindi le rotte dinamiche.

Una rotta dinamica viene definita aggiungendo i due punti (:) davanti al nome del parametro nel percorso. Ad esempio, /user/:id indica che :id è una variabile che può assumere qualsiasi valore.

Recuperare i parametri con useParams

Per leggere il valore del parametro all'interno del componente, React Router mette a disposizione l'hook useParams.

import { useParams } from 'react-router-dom';

const UserProfile = () => {
  const { id } = useParams(); // Estrae 'id' dall'URL

  return (
    <div>
      <h1>Profilo Utente</h1>
      <p>Stai visualizzando i dettagli dell'utente con ID: <strong>{id}</strong></p>
    </div>
  );
};

// Nel file App.js aggiungeremmo:
// <Route path="/user/:id" element={<UserProfile />} />

Questo approccio è fondamentale per creare pagine template che si popolan di dati in base all'ID passato nell'URL, permettendo di scalare l'applicazione a migliaia di pagine dinamiche con un unico componente.

Rotte Annidate (Nested Routes) e l'elemento Outlet

Una delle funzionalità più potenti di React Router v6 sono le rotte annidate. Esse permettono di creare layout condivisi dove solo una parte della pagina cambia in base all'URL.

Immaginiamo un'area "Dashboard" che ha un menu laterale fisso e un contenuto centrale che cambia se l'utente naviga tra "Statistiche", "Impostazioni" e "Profilo".

Implementazione del Layout con <Outlet />

Il componente Outlet funge da segnaposto: indica a React Router dove renderizzare i componenti figli della rotta corrente.

import { Routes, Route, Link, Outlet } from 'react-router-dom';

const DashboardLayout = () => {
  return (
    <div style={{ display: 'flex' }}>
      <aside style={{ width: '200px', background: '#eee', padding: '1rem' }}>
        <nav>
          <ul>
            <li><Link to="stats">Statistiche</Link></li>
            <li><Link to="settings">Impostazioni</Link></li>
          </ul>
        </nav>
      </aside>
      <main style={{ flex: 1, padding: '1rem' }}>
        {/* Qui verranno renderizzati i componenti figli */}
        <Outlet />
      </main>
    </div>
  );
};

const Stats = () => <h3>Statistiche di Vendita</h3>;
const Settings = () => <h3>Impostazioni Account</h3>;

function App() {
  return (
    <Routes>
      <Route path="/dashboard" element={<DashboardLayout />}>
        <Route path="stats" element={<Stats />} />
        <Route path="settings" element={<Settings />} />
      </Route>
    </Routes>
  );
}

In questo esempio, se l'utente naviga verso /dashboard/stats, React Router renderizzerà DashboardLayout e, all'interno del suo Outlet, inseririrà il componente Stats.

Navigazione Programmatica e Gestione degli Errori

Non sempre la navigazione avviene tramite un clic su un link. Spesso dobbiamo reindirizzare l'utente dopo un'azione, come l'invio di un form o l'effettuazione di un login.

L'hook useNavigate

Per spostare l'utente programmaticamente, utilizziamo l'hook useNavigate.

import { useNavigate } from 'react-router-dom';

const LoginForm = () => {
  const navigate = useNavigate();

  const handleLogin = async (e) => {
    e.preventDefault();
    // Simulazione chiamata API
    const success = true;
    if (success) {
      alert('Login effettuato!');
      navigate('/dashboard'); // Reindirizza alla dashboard
    }
  };

  return (
    <form
      <button type="submit">Accedi</button>
    </form>
  );
};

Gestione della Pagina 404 (Not Found)

È essenziale gestire i casi in cui l'utente inserisce un URL inesistente. In React Router, questo si ottiene definendo una rotta con path="*" alla fine della lista delle rotte. L'asterisco funge da "catch-all".

<Routes>
  <Route path="/" element={<Home />} />
  {/* ... altre rotte ... */}
  <Route path="*" element={<NotFoundPage />} />
</Routes>

Esempi Pratici: Protected Routes (Rotte Protette)

Uno dei casi d'uso più comuni è la creazione di rotte accessibili solo agli utenti autenticati. Possiamo implementare questo pattern creando un componente "wrapper" che controlla lo stato dell'autenticazione.

Implementazione del Guard Component

import { Navigate, Outlet } from 'react-router-dom';

const ProtectedRoute = ({ isAuthenticated }) => {
  if (!isAuthenticated) {
    // Se non è autenticato, reindirizza alla pagina di login
    return <Navigate to="/login" replace />;
  }

  // Se è autenticato, renderizza i componenti figli
  return <Outlet />;
};

// Utilizzo nell'App
function App() {
  const auth = false; // Questo valore verrebbe dallo stato globale (Redux, Context, etc.)

  return (
    <Routes>
      <Route path="/login" element={<Login />} />
      
      {/* Avvolgiamo le rotte protette nel wrapper */}
      <Route element={<ProtectedRoute isAuthenticated={auth} />}>
        <Route path="/admin" element={<AdminPanel />} />
        <Route path="/profile" element={<UserProfile />} />
      </Route>
    </Routes>
  );
}

In questo esempio, se isAuthenticated è false, l'utente che tenta di accedere a /admin verrà automaticamente spostato a /login. L'opzione replace evita che l'utente torni alla pagina protetta cliccando "Indietro" dopo essere stato reindirizzato.

Errori Comuni e FAQ

1. Perché il mio Link ricarica la pagina?

Questo accade solitamente se si usa il tag <a> invece di <Link> o <NavLink>. Ricorda che <a> è un elemento HTML standard che forza il browser a richiedere una nuova pagina al server, mentre <Link> intercetta l'evento e gestisce la navigazione internamente.

2. Differenza tra useNavigate e <Navigate />

  • useNavigate è un hook: si usa all'interno di funzioni (es. dopo un fetch o un evento onClick).
  • <Navigate /> è un componente: si usa all'interno del JSX per eseguire un reindirizzamento immediato durante il rendering (es. nelle rotte protette).

3. Perché le mie rotte annidate non vengono visualizzate?

L'errore più comune è dimenticare di inserire il componente <Outlet /> nel componente genitore. Senza l'Outlet, React Router sa quale rotta figlia attivare, ma non sa dove visualizzarla all'interno del layout del genitore.

Prossimi Passi e Approfondimenti

Ora che hai padroneggiato le basi e le tecniche intermedie di React Router v6, ci sono diverse aree che puoi esplorare per rendere le tue applicazioni ancora più professionali:

  1. Data Loaders e Actions (v6.4+): Le versioni più recenti di React Router hanno introdotto i loaders, che permettono di caricare i dati di una pagina prima che il componente venga renderizzato, eliminando i fastidiosi "loading spinner" all'interno dei componenti.
  2. Search Params: Impara a usare l'hook useSearchParams per gestire filtri e query string nell'URL (es. /products?category=electronics&sort=price).
  3. Animazioni di Transizione: Integra React Router con librerie come Framer Motion per creare transizioni fluide tra una pagina e l'altra.
  4. Code Splitting: Utilizza React.lazy e Suspense insieme alle rotte per caricare i componenti delle pagine solo quando l'utente vi accede, riducendo drasticamente il peso del bundle iniziale.

Per approfondire, ti consiglio la documentazione ufficiale di reactrouter.com, che rimane la risorsa più aggiornata e completa per ogni nuova release.