Cours JavaScript et React · Module 6: React Router
Architecture de routage complète - Le centre de commandement de navigation
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 :
- Couche d'authentification - AuthContext + ProtectedRoute
- Couche de navigation - NavLink avec des styles actifs
- Couche de routes - Routes imbriquées avec Outlet
- Couche de paramètres - chemins dynamiques avec useParams
- 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 NavLinkUne 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.
Navigation avec NavLink et rendu conditionnel
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ément | Rôle | Hook / Composant |
|---|---|---|
| Conteneur de routes | Active le routage | BrowserRouter |
| Définition de route | Mappe les URL aux composants | Routes, Route |
| Navigation | Liens avec styles actifs | NavLink |
| Imbrication | Mise en page partagée | Outlet, Route children |
| Paramètres dynamiques | Détails d'un élément | useParams |
| Navigation programmatique | Redirection après une action | useNavigate |
| Location state | Passage de données cachées | useLocation |
| Protection de route | Imposition de la connexion | ProtectedRoute + Navigate |
| Gestion des erreurs | Pages inexistantes | Route path="*" |
| Contexte d'auth | Données globales de l'utilisateur | createContext + 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. Comment implémente-t-on une route protégée dans React Router ?
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 !