Cours JavaScript et React · Module 6: React Router

Architecture de routage complète - Le centre de commandement de navigation

6 min de lecture
Dans cette leçon9

Navigateur, nous avons atteint l'étape finale de notre voyage à travers la ceinture d'astéroïdes ! Il est temps de rassembler toutes les techniques de navigation que nous avons apprises et de construire un système de routage complet - un véritable centre de commandement de navigation pour notre flotte cosmique.

Dans cette leçon, nous verrons comment tous les éléments de React Router fonctionnent ensemble dans une application cohérente.

Architecture d'une application complète avec routage

Lors de la construction d'une application avec un routage complet, nous devons planifier plusieurs couches :

  1. Couche d'authentification - AuthContext + ProtectedRoute
  2. Couche de navigation - NavLink avec des styles actifs
  3. Couche de routes - Routes imbriquées avec Outlet
  4. Couche de paramètres - chemins dynamiques avec useParams
  5. Couche de redirections - gestion du 404 et sécurité

Structure des dossiers

Dans les applications plus grandes, il vaut la peine d'organiser les fichiers liés au routage dans une structure logique :

1src/
2├── App.jsx              // Composant principal avec BrowserRouter
3├── routes/
4│   ├── AppRoutes.jsx    // Définition de toutes les routes
5│   └── ProtectedRoute.jsx
6├── context/
7│   └── AuthContext.jsx  // Contexte d'authentification
8├── layouts/
9│   ├── MainLayout.jsx   // Mise en page avec navigation et Outlet
10│   └── DashboardLayout.jsx
11├── pages/
12│   ├── HomePage.jsx
13│   ├── LoginPage.jsx
14│   ├── MissionDetail.jsx
15│   └── NotFound.jsx
16└── components/
17    └── Navigation.jsx   // Menu NavLink

Une telle organisation permet de gérer facilement même les applications étendues comportant des dizaines de routes.

Connecter AuthContext au routage

L'élément central est l'AuthProvider qui enveloppe toute l'application :

1function AuthProvider({ children }) {
2  const [user, setUser] = useState(null);
3
4  const login = (name) => setUser({ name });
5  const logout = () => setUser(null);
6
7  return (
8    <AuthContext.Provider value={{ user, login, logout }}>
9      {children}
10    </AuthContext.Provider>
11  );
12}
13
14// Dans App.jsx :
15function App() {
16  return (
17    <AuthProvider>
18      <BrowserRouter>
19        <Navigation />
20        <Routes>
21          {/* routes publiques et protégées */}
22        </Routes>
23      </BrowserRouter>
24    </AuthProvider>
25  );
26}

L'AuthProvider doit être à l'extérieur de BrowserRouter pour que le contexte d'authentification soit disponible dans tous les composants de route.

Le composant de navigation combine NavLink (avec des styles actifs) et AuthContext (affichage conditionnel des options) :

1function Navigation() {
2  const { user, logout } = useAuth();
3
4  return (
5    <nav>
6      <NavLink to="/" className={({isActive}) => isActive ? 'active' : ''}>
7        Accueil
8      </NavLink>
9      <NavLink to="/missions" className={({isActive}) => isActive ? 'active' : ''}>
10        Missions
11      </NavLink>
12
13      {user ? (
14        <>
15          <NavLink to="/dashboard" className={({isActive}) => isActive ? 'active' : ''}>
16            Dashboard
17          </NavLink>
18          <span>Connecté : {user.name}</span>
19          <button onClick={logout}>Déconnexion</button>
20        </>
21      ) : (
22        <NavLink to="/login">Connexion</NavLink>
23      )}
24    </nav>
25  );
26}

Ainsi, la navigation réagit automatiquement à l'état d'authentification - un utilisateur connecté voit le Dashboard et le bouton de déconnexion, tandis qu'un utilisateur non authentifié voit le lien de connexion.

Routes imbriquées avec mise en page

La section des missions utilise des routes imbriquées avec Outlet :

1function MissionsLayout() {
2  return (
3    <div className="missions-layout">
4      <aside>
5        <h3>Missions</h3>
6        <NavLink to="/missions/apollo">Apollo</NavLink>
7        <NavLink to="/missions/artemis">Artemis</NavLink>
8        <NavLink to="/missions/voyager">Voyager</NavLink>
9      </aside>
10      <main>
11        <Outlet />
12      </main>
13    </div>
14  );
15}
16
17// Dans la configuration des routes :
18<Route path="/missions" element={<MissionsLayout />}>
19  <Route index element={<MissionsList />} />
20  <Route path=":missionId" element={<MissionDetail />} />
21</Route>

La route index (<Route index />) rend le contenu par défaut (liste des missions) lorsque l'utilisateur est sur /missions. Le paramètre dynamique :missionId gère les détails de chaque mission.

Configuration complète des routes

Voici à quoi ressemble la configuration complète des routes combinant toutes les techniques que nous avons apprises :

1function App() {
2  return (
3    <AuthProvider>
4      <BrowserRouter>
5        <Navigation />
6        <Routes>
7          {/* Page d'accueil */}
8          <Route path="/" element={<HomePage />} />
9
10          {/* Connexion */}
11          <Route path="/login" element={<LoginPage />} />
12
13          {/* Routes imbriquées avec mise en page */}
14          <Route path="/missions" element={<MissionsLayout />}>
15            <Route index element={<MissionsList />} />
16            <Route path=":missionId" element={<MissionDetail />} />
17          </Route>
18
19          {/* Route protégée */}
20          <Route path="/dashboard" element={
21            <ProtectedRoute>
22              <DashboardPage />
23            </ProtectedRoute>
24          } />
25
26          {/* Gestion du 404 */}
27          <Route path="*" element={<NotFound />} />
28        </Routes>
29      </BrowserRouter>
30    </AuthProvider>
31  );
32}

L'ordre des éléments a son importance :

  • Les chemins spécifiques (/, /login, /missions) sont d'abord mis en correspondance
  • La route path="*" doit être en dernier - elle capture tout ce qui ne correspond à aucune route définie

Connexion avec mémorisation du chemin

La page de connexion utilise useNavigate et useLocation pour rediriger l'utilisateur vers la page d'où il a été redirigé :

1function LoginPage() {
2  const [name, setName] = useState('');
3  const { login } = useAuth();
4  const navigate = useNavigate();
5  const location = useLocation();
6
7  // Chemin d'où l'utilisateur a été redirigé
8  const from = location.state?.from?.pathname || '/dashboard';
9
10  const handleSubmit = (e) => {
11    e.preventDefault();
12    if (name.trim()) {
13      login(name);
14      navigate(from, { replace: true }); // replace: true supprime /login de l'historique
15    }
16  };
17
18  return (
19    <form onSubmit={handleSubmit}>
20      <input value={name} onChange={(e) => setName(e.target.value)} />
21      <button type="submit">Se connecter</button>
22    </form>
23  );
24}

L'option replace: true dans navigate est cruciale - grâce à elle, l'utilisateur ne reviendra pas à la page de connexion après avoir cliqué sur "Retour" dans le navigateur.

Gestion de la page 404

La page Not Found contient un lien de retour vers la page d'accueil :

1function NotFound() {
2  return (
3    <div className="not-found">
4      <h1>404</h1>
5      <p>Page introuvable</p>
6      <Link to="/">Retour à la page d'accueil</Link>
7    </div>
8  );
9}

Résumé de l'architecture

Une application complète avec routage dans React combine les éléments suivants :

ÉlémentRôleHook / Composant
Conteneur de routesActive le routageBrowserRouter
Définition de routeMappe les URL aux composantsRoutes, Route
NavigationLiens avec styles actifsNavLink
ImbricationMise en page partagéeOutlet, Route children
Paramètres dynamiquesDétails d'un élémentuseParams
Navigation programmatiqueRedirection après une actionuseNavigate
Location statePassage de données cachéesuseLocation
Protection de routeImposition de la connexionProtectedRoute + Navigate
Gestion des erreursPages inexistantesRoute path="*"
Contexte d'authDonnées globales de l'utilisateurcreateContext + useContext

Essaie l'application complète dans l'éditeur ci-dessous - connecte-toi, navigue entre les missions, consulte le Dashboard et teste la page 404 !

Code de cette leçon : App.jsx
1import React, { useState, createContext, useContext, Suspense, lazy } from 'react';
2import {
3  BrowserRouter, Routes, Route, Link, NavLink,
4  useParams, useNavigate, useLocation, Navigate, Outlet
5} from 'react-router-dom';
6
7// Auth Context
8const AuthContext = createContext();
9function useAuth() { return useContext(AuthContext); }
10
11function AuthProvider({ children }) {
12  const [user, setUser] = useState(null);
13  const login = (name) => setUser({ name });
14  const logout = () => setUser(null);
15  return (
16    <AuthContext.Provider value={{ user, login, logout }}>
17      {children}
18    </AuthContext.Provider>
19  );
20}
21
22// Protected Route
23function ProtectedRoute({ children }) {
24  const { user } = useAuth();
25  const location = useLocation();
26  if (!user) return <Navigate to="/login" state={{ from: location }} replace />;
27  return children;
28}
29
30// Composants
31function Navigation() {
32  const { user, logout } = useAuth();
33  return (
34    <nav>
35      <div className="nav-brand">SpaceApp</div>
36      <div className="nav-links">
37        <NavLink to="/" className={({isActive}) => isActive ? 'active' : ''}>Accueil</NavLink>
38        <NavLink to="/missions" className={({isActive}) => isActive ? 'active' : ''}>Missions</NavLink>
39        {user ? (
40          <>
41            <NavLink to="/dashboard" className={({isActive}) => isActive ? 'active' : ''}>Dashboard</NavLink>
42            <span className="user-badge">{user.name}</span>
43            <button onClick={logout}>Déconnexion</button>
44          </>
45        ) : (
46          <NavLink to="/login" className={({isActive}) => isActive ? 'active' : ''}>Connexion</NavLink>
47        )}
48      </div>
49    </nav>
50  );
51}
52
53function HomePage() {
54  return (
55    <div className="page home">
56      <h1>Bienvenue dans SpaceApp</h1>
57      <p>Ta porte d'entrée vers l'exploration spatiale</p>
58      <div className="features">
59        <div className="feature">Suivre les missions</div>
60        <div className="feature">Voir les analyses</div>
61        <div className="feature">Gérer l'équipage</div>
62      </div>
63    </div>
64  );
65}
66
67function LoginPage() {
68  const [name, setName] = useState('');
69  const { login } = useAuth();
70  const navigate = useNavigate();
71  const location = useLocation();
72  const from = location.state?.from?.pathname || '/dashboard';
73
74  const handleSubmit = (e) => {
75    e.preventDefault();
76    if (name.trim()) {
77      login(name);
78      navigate(from, { replace: true });
79    }
80  };
81
82  return (
83    <div className="page login">
84      <h1>Connexion</h1>
85      <form onSubmit={handleSubmit}>
86        <input value={name} onChange={(e) => setName(e.target.value)} placeholder="Nom d'utilisateur" />
87        <button type="submit">Se connecter</button>
88      </form>
89    </div>
90  );
91}
92
93function MissionsLayout() {
94  return (
95    <div className="missions-layout">
96      <aside>
97        <h3>Missions</h3>
98        <NavLink to="/missions/apollo">Apollo</NavLink>
99        <NavLink to="/missions/artemis">Artemis</NavLink>
100        <NavLink to="/missions/voyager">Voyager</NavLink>
101      </aside>
102      <main><Outlet /></main>
103    </div>
104  );
105}
106
107function MissionsList() {
108  return <div className="content"><h2>Sélectionne une mission</h2></div>;
109}
110
111function MissionDetail() {
112  const { missionId } = useParams();
113  return (
114    <div className="content">
115      <h2>Mission : {missionId?.toUpperCase()}</h2>
116      <p>Détails de la mission {missionId}</p>
117    </div>
118  );
119}
120
121function DashboardPage() {
122  const { user } = useAuth();
123  return (
124    <div className="page dashboard">
125      <h1>Dashboard</h1>
126      <p>Bon retour, {user?.name} !</p>
127      <div className="stats">
128        <div className="stat"><span>Missions</span>12</div>
129        <div className="stat"><span>Équipage</span>48</div>
130        <div className="stat"><span>Vaisseaux</span>8</div>
131      </div>
132    </div>
133  );
134}
135
136function NotFound() {
137  return (
138    <div className="page not-found">
139      <h1>404</h1>
140      <p>Page introuvable</p>
141      <Link to="/">Aller à l'accueil</Link>
142    </div>
143  );
144}
145
146function App() {
147  return (
148    <AuthProvider>
149      <BrowserRouter>
150        <div className="app">
151          <Navigation />
152          <Routes>
153            <Route path="/" element={<HomePage />} />
154            <Route path="/login" element={<LoginPage />} />
155            <Route path="/missions" element={<MissionsLayout />}>
156              <Route index element={<MissionsList />} />
157              <Route path=":missionId" element={<MissionDetail />} />
158            </Route>
159            <Route path="/dashboard" element={
160              <ProtectedRoute><DashboardPage /></ProtectedRoute>
161            } />
162            <Route path="*" element={<NotFound />} />
163          </Routes>
164        </div>
165      </BrowserRouter>
166    </AuthProvider>
167  );
168}
169
170export default App;

Tu as repéré une erreur dans cette leçon ?

Teste tes connaissances

Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.

  1. 1. Comment implémente-t-on une route protégée dans React Router ?

  2. 2. Où dans le composant parent places-tu <Outlet /> pour rendre les routes imbriquées ?

Voici 2 des 7 questions de cette leçon. Réponds aux autres dans le jeu.

Tâches pratiques dans le jeu

  • Éditeur de code

    Implémente la gestion des erreurs 404 avec Route path='*' et un composant NotFound. Ajoute une navigation avec des liens vers des pages existantes et inexistantes pour tester la gestion des erreurs.

  • Clique dans l'ordre

    Ordonne la navigation programmatique avec état :

  • Tri vertical

    Ordonne les étapes de la navigation programmatique à l'aide de useNavigate :

  • Tri vertical

    Ordonne les étapes pour passer et lire des données entre les routes :

  • Tri vertical

    Ordonne les étapes d'implémentation d'une route protégée avec le composant Navigate :

  • Clique dans l'ordre

    Ordonne la gestion des paramètres de recherche :

  • Tri vertical

    Ordonne les étapes de la navigation dans React Router, du clic sur un lien à l'affichage de la page :

  • Tri vertical

    Ordonne les étapes de gestion d'un accès non autorisé à une route protégée :

  • Tri horizontal

    Ordonne les éléments d'une route gérant la page 404 :

  • Tri horizontal

    Ordonne la syntaxe d'une Route avec un paramètre id dynamique :

  • Éditeur de code

    Construis un système de navigation complet incluant : la configuration de React Router avec BrowserRouter, des routes imbriquées pour la structure /fleet/:id/missions/:missionId, du routage dynamique avec params, les composants Link et NavLink avec des styles actifs, useNavigate pour la navigation programmatique, les hooks useParams et useLocation, des routes protégées avec authentification, le chargement différé avec React.lazy et Suspense, les paramètres de recherche avec useSearchParams, et la gestion des erreurs 404. Crée un système de navigation digne de la flotte la plus avancée traversant la ceinture d'astéroïdes !

Articles utiles