Gérer le Routage avec React Router
Bienvenue à cette leçon fondamentale du cours "Maîtriser React.js : Construire des Interfaces Utilisateur Modernes et Réactives". Aujourd'hui, nous allons plonger dans l'art de la navigation au sein d'une application React, un aspect crucial pour toute interface utilisateur moderne et réactive : le routage. Plus précisément, nous allons explorer en profondeur React Router, la bibliothèque de routage la plus populaire et la plus puissante pour React.
Introduction au Routage dans les Applications Monopages (SPA)
Traditionnellement, lorsqu'un utilisateur cliquait sur un lien hypertexte sur un site web, le navigateur rechargeait une nouvelle page complète depuis le serveur. Ce modèle, bien que simple, entraîne un rechargement complet de l'interface, une perte d'état et une expérience utilisateur souvent perçue comme lente et saccadée.
Les Applications Monopages (SPA - Single Page Applications), dont React est un excellent exemple, fonctionnent différemment. L'idée est de charger une seule page HTML initiale, puis de modifier dynamiquement son contenu en fonction des interactions de l'utilisateur, sans jamais recharger la page entière. Cela offre une expérience utilisateur fluide, rapide et interactive, plus proche d'une application de bureau que d'un site web traditionnel.
Le Défi du Routage dans les SPA
Si nous ne rechargeons jamais la page, comment gérons-nous la navigation ? Comment faire en sorte que l'URL change lorsque l'utilisateur "navigue" vers une nouvelle "page" logique (qui n'est en fait qu'un nouveau composant ou un ensemble de composants) ? Et comment permettre aux utilisateurs de mettre en signet des états spécifiques de notre application, ou d'utiliser les boutons "précédent" et "suivant" de leur navigateur ?
C'est là qu'intervient le routage côté client. Au lieu de demander une nouvelle page au serveur, le navigateur intercepte la navigation, met à jour l'URL (sans rechargement) et une bibliothèque de routage prend le relais pour afficher le bon composant React en fonction de l'URL actuelle.
Pourquoi React Router ?
React Router est devenu la solution de facto pour le routage déclaratif dans React. Il offre une API riche et flexible qui s'intègre parfaitement avec le paradigme des composants de React. Grâce à lui, vous pouvez :
- Définir des routes claires et lisibles qui mappent des chemins URL à des composants React.
- Naviguer programmatiquement ou via des liens.
- Gérer des paramètres d'URL et des requêtes.
- Implémenter des routes imbriquées pour des structures complexes.
- Assurer la compatibilité avec les fonctionnalités du navigateur (historique, favoris).
Dans cette leçon, nous allons nous concentrer sur React Router v6, la dernière version majeure, qui apporte des améliorations significatives en termes de simplicité et de performances par rapport aux versions précédentes.
Installation et Premiers Pas avec React Router
Avant de pouvoir utiliser React Router, vous devez l'installer dans votre projet.
1. Installation
Ouvrez votre terminal dans le répertoire de votre projet React et exécutez la commande suivante :
npm install react-router-dom
# ou si vous utilisez yarn
yarn add react-router-dom
react-router-dom est le package que vous utiliserez dans les applications web. Il inclut react-router et ajoute des composants spécifiques au DOM comme BrowserRouter et Link.
2. Configuration de Base : Le Router Principal
Toute application React utilisant React Router doit être enveloppée dans un composant Router. Le plus couramment utilisé est BrowserRouter.
Créez ou modifiez votre fichier src/index.js (ou src/main.jsx si vous utilisez Vite et React 18+) pour y inclure le BrowserRouter.
// src/index.js ou src/main.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom'; // Importez BrowserRouter
import App from './App';
import './index.css';
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
{/* Enveloppez votre application dans BrowserRouter */}
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);
BrowserRouter vs HashRouter
BrowserRouter: Utilise l'API History du navigateur (HTML5 pushState) pour garder l'interface utilisateur synchronisée avec l'URL. C'est la méthode préférée pour la plupart des applications web modernes car elle génère des URL "propres" (ex:monsite.com/about). Cela nécessite une configuration côté serveur pour gérer les rechargements directs d'URL (en redirigeant toutes les requêtes vers votreindex.html).HashRouter: Utilise le "hash" de l'URL (ex:monsite.com/#/about). Cela ne nécessite aucune configuration serveur car tout ce qui se trouve après le#n'est pas envoyé au serveur. Cependant, les URL sont moins esthétiques et le#n'est pas inclus dans la requête envoyée au serveur, ce qui peut poser problème pour certains services. À utiliser si vous ne pouvez pas contrôler la configuration de votre serveur.
Pour le reste de cette leçon, nous utiliserons BrowserRouter.
Composants Clés de React Router v6
React Router v6 a simplifié son API en consolidant et en renommant certains composants. Voici les éléments essentiels que vous utiliserez.
Routes et Route
Ces deux composants sont le cœur de la définition de vos itinéraires.
<Routes>: Remplace l'ancien<Switch>de v5. Il est responsable de ne rendre qu'une seule de ses routes enfants qui correspond à l'URL actuelle.<Route>: Définit un itinéraire individuel. Il prend deux props principales :path: Le chemin URL à faire correspondre.element: Le composant React à rendre lorsque le chemin correspond.
Exemple :
// src/App.js
import React from 'react';
import { Routes, Route, Link } from 'react-router-dom';
import HomePage from './components/HomePage';
import AboutPage from './components/AboutPage';
import ContactPage from './components/ContactPage';
function App() {
return (
<div>
<nav>
<ul>
<li><Link to="/">Accueil</Link></li>
<li><Link to="/about">À Propos</Link></li>
<li><Link to="/contact">Contact</Link></li>
</ul>
</nav>
{/* Le composant Routes va rendre le bon composant en fonction de l'URL */}
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/about" element={<AboutPage />} />
<Route path="/contact" element={<ContactPage />} />
</Routes>
</div>
);
}
export default App;
// src/components/HomePage.js
import React from 'react';
const HomePage = () => <h1>Bienvenue sur la Page d'Accueil !</h1>;
export default HomePage;
// src/components/AboutPage.js
import React from 'react';
const AboutPage = () => <h1>À Propos de Nous</h1>;
export default AboutPage;
// src/components/ContactPage.js
import React from 'react';
const ContactPage = () => <h1>Contactez-nous</h1>;
export default ContactPage;
Dans cet exemple, App.js définit la navigation et les routes. Lorsque l'URL est /, HomePage est rendu ; /about rend AboutPage, et ainsi de suite.
Link et NavLink
Ces composants sont utilisés pour créer des liens de navigation dans votre application. Ils empêchent le rechargement complet de la page et permettent à React Router de gérer la navigation.
<Link to="/chemin">Nom du Lien</Link>: Le composant de base pour la navigation. Il rend un tag<a>HTML standard.<NavLink to="/chemin">Nom du Lien</NavLink>: Une version spéciale deLinkqui ajoute des fonctionnalités pour styliser un lien actif. Il est très utile pour mettre en évidence la page actuellement visitée dans une barre de navigation.
Stylisation avec NavLink
NavLink ajoute la classe active par défaut à l'élément <a> quand son to correspond à l'URL actuelle. Vous pouvez personnaliser cette classe ou utiliser une fonction pour un style plus complexe.
// Exemple de navigation avec NavLink
import { NavLink } from 'react-router-dom';
import './Navbar.css'; // Votre fichier CSS pour le style
function Navbar() {
return (
<nav>
<ul>
<li>
<NavLink
to="/"
className={({ isActive }) => (isActive ? 'active-link' : undefined)}
>
Accueil
</NavLink>
</li>
<li>
<NavLink
to="/dashboard"
className={({ isActive }) => (isActive ? 'active-link' : undefined)}
>
Tableau de Bord
</NavLink>
</li>
</ul>
</nav>
);
}
export default Navbar;
/* src/Navbar.css */
.active-link {
font-weight: bold;
color: #007bff; /* Couleur bleue pour le lien actif */
text-decoration: underline;
}
nav ul {
list-style: none;
padding: 0;
display: flex;
gap: 15px;
}
nav a {
text-decoration: none;
color: #333;
}
nav a:hover {
color: #007bff;
}
Hooks de React Router
React Router v6 exploite pleinement les Hooks de React pour accéder à l'état du routeur et effectuer des actions.
useNavigate() : Navigation Programmatique
Ce hook renvoie une fonction qui vous permet de naviguer vers d'autres routes de manière programmatique, par exemple après la soumission d'un formulaire, une connexion réussie, ou un clic sur un bouton non-lien.
import { useNavigate } from 'react-router-dom';
function LoginForm() {
const navigate = useNavigate();
const handleSubmit = (event) => {
event.preventDefault();
// Logique de connexion...
const loginSuccess = true; // Simule une connexion réussie
if (loginSuccess) {
navigate('/dashboard'); // Redirige vers le tableau de bord
}
};
return (
<form onSubmit={handleSubmit}>
{/* Champs du formulaire */}
<button type="submit">Se connecter</button>
<button type="button" onClick={() => navigate(-1)}>Retour</button> {/* Revient à la page précédente */}
</form>
);
}
navigate(path, { replace: true }) : L'option replace: true remplace l'entrée actuelle dans l'historique de navigation au lieu d'en ajouter une nouvelle. Utile pour les redirections après connexion, par exemple, pour que l'utilisateur ne puisse pas revenir à la page de connexion via le bouton "précédent".
useParams() : Accéder aux Paramètres d'URL
Pour les routes dynamiques (ex: /products/123, /users/john-doe), useParams vous permet d'extraire les segments de l'URL définis comme paramètres.
import { useParams } from 'react-router-dom';
function UserProfile() {
const { userId } = useParams(); // 'userId' correspond au nom du paramètre dans la route
return (
<div>
<h1>Profil de l'utilisateur : {userId}</h1>
{/* Afficher les détails de l'utilisateur avec l'ID */}
</div>
);
}
Pour que cela fonctionne, votre Route doit être définie avec un paramètre :
<Routes>
{/* ... autres routes ... */}
<Route path="/users/:userId" element={<UserProfile />} /> {/* :userId est le paramètre */}
</Routes>
useLocation() : Accéder à l'Objet location
Ce hook renvoie l'objet location qui représente l'URL actuelle. Il contient des informations comme :
pathname: Le chemin de l'URL (ex:/products/123).search: La chaîne de requête (ex:?category=electronics&page=1).hash: Le fragment d'URL (ex:#section1).state: Des données optionnelles passées vianavigateouLink.key: Une clé unique pour l'emplacement, utile pour forcer des re-rendus ou des animations.
import { useLocation } from 'react-router-dom';
function CurrentLocationInfo() {
const location = useLocation();
return (
<div>
<p>Chemin actuel : {location.pathname}</p>
<p>Chaîne de requête : {location.search}</p>
<p>Hash : {location.hash}</p>
{location.state && <p>État passé : {JSON.stringify(location.state)}</p>}
</div>
);
}
// Exemple de passage d'état avec Link
// <Link to="/checkout" state={{ fromProduct: true, productId: 456 }}>Passer à la caisse</Link>
useSearchParams() : Gérer les Paramètres de Requête
Ce hook est spécifiquement conçu pour lire et modifier les paramètres de requête (query parameters) dans l'URL (ex: ?sort=name&page=2). Il renvoie un tableau contenant un objet URLSearchParams et une fonction pour le mettre à jour.
import { useSearchParams } from 'react-router-dom';
function ProductsList() {
const [searchParams, setSearchParams] = useSearchParams();
const sortBy = searchParams.get('sort') || 'price'; // Lire le paramètre 'sort'
const page = parseInt(searchParams.get('page') || '1'); // Lire le paramètre 'page'
const handleSortChange = (newSort) => {
setSearchParams(prevParams => {
prevParams.set('sort', newSort);
return prevParams;
});
};
const handlePageChange = (newPage) => {
setSearchParams(prevParams => {
prevParams.set('page', newPage);
return prevParams;
});
};
return (
<div>
<h1>Produits</h1>
<p>Tri actuel : {sortBy}</p>
<p>Page actuelle : {page}</p>
<button onClick={() => handleSortChange('name')}>Trier par Nom</button>
<button onClick={() => handleSortChange('price')}>Trier par Prix</button>
<button onClick={() => handlePageChange(page + 1)}>Page Suivante</button>
</div>
);
}
Routes Imbriquées (Nested Routes)
Un concept puissant de React Router est la capacité de définir des routes imbriquées. Cela signifie qu'un composant rendu par une route peut lui-même contenir d'autres routes. C'est idéal pour les layouts complexes où des sections spécifiques d'une page ont leur propre navigation et leurs propres sous-sections (ex: un tableau de bord avec plusieurs onglets, ou un profil utilisateur avec des pages "historique", "paramètres", "messages").
Pour les routes imbriquées, le chemin du parent doit se terminer par un /* ou ne pas se terminer par un / pour permettre l'imbrication des chemins enfants. Cependant, en v6, la meilleure pratique est de ne pas mettre de /* et de définir les chemins enfants relatifs au chemin du parent.
Le composant spécial <Outlet /> est utilisé dans le composant parent pour indiquer où les routes enfants doivent être rendues.
Exemple de routes imbriquées :
Imaginons un tableau de bord avec des sections pour les "Statistiques" et les "Paramètres".
// src/App.js (Routes parentes)
import React from 'react';
import { Routes, Route, Link } from 'react-router-dom';
import HomePage from './components/HomePage';
import DashboardLayout from './components/DashboardLayout'; // Le composant qui contiendra les routes imbriquées
import NotFoundPage from './components/NotFoundPage'; // Pour la 404
function App() {
return (
<div>
<nav>
<ul>
<li><Link to="/">Accueil</Link></li>
<li><Link to="/dashboard">Tableau de Bord</Link></li>
</ul>
</nav>
<Routes>
<Route path="/" element={<HomePage />} />
{/* Route parente pour le tableau de bord */}
<Route path="/dashboard/*" element={<DashboardLayout />} />
{/*
Note: Avec v6, le '*' est implicite pour les routes parentes
qui contiennent des routes enfants définies RELATIVEMENT.
Cependant, le laisser ici peut être plus explicite ou nécessaire pour certaines configurations avancées.
Pour les routes imbriquées simples, il n'est pas toujours nécessaire et la route enfant doit juste être relative.
Une meilleure pratique en v6 est de ne pas mettre le '*' et de s'assurer que les chemins des enfants sont simplement relatifs,
par exemple `<Route path="stats" ... />` pour `/dashboard/stats`.
Pour cette démo, nous utilisons `path="/dashboard"` dans le parent, et les enfants se définissent en relatif dans DashboardLayout.
*/}
<Route path="*" element={<NotFoundPage />} /> {/* Route 404 */}
</Routes>
</div>
);
}
export default App;
// src/components/DashboardLayout.js
import React from 'react';
import { Outlet, Link, Routes, Route } from 'react-router-dom'; // Important: Routes et Route sont aussi utilisés ici
import DashboardStats from './DashboardStats';
import DashboardSettings from './DashboardSettings';
function DashboardLayout() {
return (
<div>
<h2>Mon Tableau de Bord</h2>
<nav>
<ul>
<li><Link to="/dashboard">Statistiques</Link></li> {/* Chemin absolu ou relatif à la racine /dashboard */}
<li><Link to="settings">Paramètres</Link></li> {/* Chemin relatif à la route parente (/dashboard/settings) */}
</ul>
</nav>
<hr />
{/* Les routes imbriquées seront rendues ici */}
<Outlet />
{/* Vous pouvez aussi définir les routes imbriquées directement ici si vous préférez */}
{/* Alternativement, on peut les passer comme children à <Route> dans App.js */}
{/* Exemple de routes imbriquées directement dans le composant qui contient l'Outlet */}
<Routes>
{/* Route Index : rend ce composant quand le chemin est exactement /dashboard */}
<Route index element={<DashboardStats />} />
<Route path="settings" element={<DashboardSettings />} />
</Routes>
</div>
);
}
export default DashboardLayout;
// src/components/DashboardStats.js
import React from 'react';
const DashboardStats = () => <h3>Vue des Statistiques</h3>;
export default DashboardStats;
// src/components/DashboardSettings.js
import React from 'react';
const DashboardSettings = () => <h3>Gérer les Paramètres</h3>;
export default DashboardSettings;
// src/components/NotFoundPage.js
import React from 'react';
const NotFoundPage = () => (
<div>
<h1>404 - Page Non Trouvée</h1>
<p>Désolé, cette page n'existe pas.</p>
</div>
);
export default NotFoundPage;
Dans cet exemple, lorsque l'URL est /dashboard, DashboardLayout est rendu. Si l'URL est /dashboard/settings, DashboardLayout est toujours rendu, et le <Outlet /> à l'intérieur de DashboardLayout sera remplacé par le DashboardSettings correspondant.
Notez l'utilisation de index sur la première route imbriquée : <Route index element={<DashboardStats />} />. Cela signifie que DashboardStats sera rendu lorsque le chemin correspond exactement au chemin du parent (/dashboard), sans aucun segment supplémentaire. C'est l'équivalent de path="" dans les versions précédentes.
Gestion des Erreurs (Route 404)
Pour gérer les URL non correspondantes (pages introuvables ou 404), vous pouvez utiliser un chemin * (astérisque) comme dernière route dans votre bloc Routes. Cette route agira comme un "fourre-tout" et se déclenchera si aucune autre route n'a correspondu.
// Dans votre fichier App.js ou là où vous avez vos Routes principales
import { Routes, Route } from 'react-router-dom';
import HomePage from './components/HomePage';
import AboutPage from './components/AboutPage';
import NotFoundPage from './components/NotFoundPage'; // Créez ce composant
function App() {
return (
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/about" element={<AboutPage />} />
{/* Cette route doit toujours être la dernière */}
<Route path="*" element={<NotFoundPage />} />
</Routes>
);
}
Bonnes Pratiques et Conseils
-
Organisation des Routes : Pour les grandes applications, il est judicieux de découper vos définitions de routes en fichiers séparés ou d'utiliser des objets pour les définir, puis de les rendre dynamiquement.
-
Lazy Loading (Chargement Paresseux) : Pour améliorer les performances des grandes applications, vous pouvez charger des composants de route uniquement lorsqu'ils sont nécessaires, en utilisant
React.lazy()et<Suspense>.// App.js import React, { Suspense, lazy } from 'react'; import { Routes, Route } from 'react-router-dom'; const HomePage = lazy(() => import('./components/HomePage')); const AboutPage = lazy(() => import('./components/AboutPage')); const UserProfile = lazy(() => import('./components/UserProfile')); function App() { return ( <Suspense fallback={<div>Chargement...</div>}> <Routes> <Route path="/" element={<HomePage />} /> <Route path="/about" element={<AboutPage />} /> <Route path="/users/:userId" element={<UserProfile />} /> {/* ... autres routes ... */} </Routes> </Suspense> ); } -
Sécurité et Authentification : Pour les routes protégées, vous devrez implémenter une logique pour vérifier l'authentification de l'utilisateur avant de rendre le composant de la route, souvent en créant un composant "wrapper" ou un custom hook.
-
Accessibilité : Assurez-vous que votre navigation est accessible. Utilisez des balises sémantiques et testez la navigation au clavier.
NavLinkaide déjà avec les états actifs.
Conclusion
Le routage est la colonne vertébrale de la navigation dans toute application web moderne. Avec React Router, vous disposez d'un outil puissant et flexible pour créer des expériences utilisateur fluides et réactives dans vos applications React.
Nous avons couvert les concepts essentiels :
- L'installation et la configuration de base avec
BrowserRouter. - Les composants clés comme
Routes,Route,Link, etNavLink. - Les Hooks fondamentaux :
useNavigatepour la navigation programmatique,useParamspour extraire les données de l'URL,useLocationpour l'information sur l'URL, etuseSearchParamspour les paramètres de requête. - La puissance des routes imbriquées avec
<Outlet />pour des layouts complexes. - La gestion des erreurs 404.
En maîtrisant React Router, vous êtes désormais équipé pour construire des applications React plus complexes et professionnelles, offrant une navigation intuitive et performante à vos utilisateurs. Entraînez-vous à implémenter ces concepts dans vos propres projets pour solidifier votre compréhension.