Cours JavaScript et React · Module 6: React Router

Passer un état à travers les emplacements dans la communication intergalactique

9 min de lecture
Dans cette leçon9

Pendant un voyage cosmique, nous avons parfois besoin d'envoyer des données importantes d'un secteur de la station spatiale à un autre. Dans les applications React utilisant React Router, nous passons de la même manière un état (des données) entre différentes pages à l'aide du mécanisme de location state. Voyons comment utiliser ce mécanisme pour améliorer notre communication intergalactique.

Qu'est-ce que le location state ?

Le location state est un mécanisme de React Router qui permet de passer des données lors de la navigation entre différents chemins sans avoir besoin d'utiliser des paramètres d'URL, des cookies ou un état global de l'application. Ces données ne sont pas visibles dans l'URL, mais sont disponibles sur la page cible.

La façon la plus simple de passer des données via le location state est d'utiliser le composant Link avec un paramètre supplémentaire state :

1import { Link } from 'react-router-dom';
2
3function PlanetList() {
4  const planets = [
5    { id: 1, name: 'Mars', atmosphere: 'Mince, principalement du CO2', temperature: 'De -125°C à 20°C' },
6    { id: 2, name: 'Vénus', atmosphere: 'Dense, principalement du CO2', temperature: 'Moyenne de 462°C' },
7    { id: 3, name: 'Titan', atmosphere: 'Dense, azote et méthane', temperature: 'Moyenne de -179°C' }
8  ];
9
10  return (
11    <div className="planet-explorer">
12      <h1>Exploration des planètes</h1>
13      <ul className="planet-list">
14        {planets.map(planet => (
15          <li key={planet.id}>
16            <Link
17              to={`/planet/${planet.id}`}
18              state={{ planetData: planet }}
19            >
20              Explorer {planet.name}
21            </Link>
22          </li>
23        ))}
24      </ul>
25    </div>
26  );
27}

Dans l'exemple ci-dessus, cliquer sur un lien naviguera l'utilisateur vers la page /planet/1 (pour Mars) et passera simultanément l'objet planetData contenant toutes les informations sur la planète.

Recevoir l'état dans le composant cible

Pour lire l'état passé dans le composant cible, nous utilisons le hook useLocation :

1import { useParams, useLocation } from 'react-router-dom';
2
3function PlanetDetails() {
4  // Obtenir le paramètre depuis l'URL
5  const { id } = useParams();
6
7  // Obtenir l'objet location, qui contient l'état passé
8  const location = useLocation();
9
10  // Extraire les données de la planète depuis le location state, avec un repli sur un objet vide
11  const { planetData } = location.state || {};
12
13  // Si nous n'avons pas reçu de données de planète, nous pouvons afficher un message d'erreur
14  // ou tenter de récupérer les données en fonction de l'ID
15  if (!planetData) {
16    return (
17      <div className="error-message">
18        <h2>Aucune donnée de planète</h2>
19        <p>Impossible de charger les données pour la planète avec l'ID : {id}</p>
20        <Link to="/">Retour à la liste des planètes</Link>
21      </div>
22    );
23  }
24
25  return (
26    <div className="planet-details">
27      <h1>Détails de la planète : {planetData.name}</h1>
28
29      <div className="planet-info">
30        <p><strong>Atmosphère :</strong> {planetData.atmosphere}</p>
31        <p><strong>Température :</strong> {planetData.temperature}</p>
32      </div>
33
34      <Link to="/">Retour à la liste des planètes</Link>
35    </div>
36  );
37}

Passer un état de manière programmatique

Nous pouvons également passer un état de manière programmatique à l'aide du hook useNavigate :

1import { useNavigate } from 'react-router-dom';
2
3function MissionControl() {
4  const navigate = useNavigate();
5
6  const startMission = (mission) => {
7    // Effectuer quelques opérations avant de naviguer vers la page suivante
8    const missionData = {
9      ...mission,
10      startDate: new Date().toISOString(),
11      commander: 'Capitaine Alex Chen',
12      status: 'En cours'
13    };
14
15    // Rediriger vers la page de la mission avec les données
16    navigate(`/mission/${mission.id}`, {
17      state: { missionData }
18    });
19  };
20
21  return (
22    <div className="mission-control">
23      <h1>Centre de commandement de mission</h1>
24
25      <div className="mission-list">
26        <div className="mission-card">
27          <h3>Mission : Exploration de Mars</h3>
28          <p>Objectif : Recherche d'échantillons de sol</p>
29          <button onClick={() => startMission({
30            id: 'mars-exploration-1',
31            name: 'Exploration de Mars',
32            objective: 'Recherche d\'échantillons de sol'
33          })}>
34            Démarrer la mission
35          </button>
36        </div>
37
38        <div className="mission-card">
39          <h3>Mission : Station satellite</h3>
40          <p>Objectif : Réparation des systèmes de communication</p>
41          <button onClick={() => startMission({
42            id: 'satellite-repair-1',
43            name: 'Station satellite',
44            objective: 'Réparation des systèmes de communication'
45          })}>
46            Démarrer la mission
47          </button>
48        </div>
49      </div>
50    </div>
51  );
52}

Cas d'usage avancés

1. Passer l'information de l'emplacement précédent

Nous pouvons utiliser le location state pour mémoriser la page d'où vient l'utilisateur :

1function SecureArea() {
2  const navigate = useNavigate();
3  const { user } = useAuth();
4
5  useEffect(() => {
6    if (!user) {
7      // Rediriger vers la connexion avec l'information sur le chemin actuel
8      navigate('/login', {
9        state: { from: location.pathname },
10        replace: true
11      });
12    }
13  }, [user, navigate, location]);
14
15  return (
16    <div className="secure-area">
17      <h1>Section sécurisée du vaisseau</h1>
18      {/* Contenu de la section sécurisée */}
19    </div>
20  );
21}
22
23// Dans le composant de connexion
24function Login() {
25  const location = useLocation();
26  const navigate = useNavigate();
27
28  // Obtenir le chemin d'origine auquel l'utilisateur a tenté d'accéder
29  const from = location.state?.from || '/dashboard';
30
31  const handleLogin = async (credentials) => {
32    const success = await performLogin(credentials);
33
34    if (success) {
35      // Après la connexion, revenir à la page d'origine
36      navigate(from, { replace: true });
37    }
38  };
39
40  // Reste du composant de connexion...
41}

2. Passer les données d'un formulaire dans un processus multi-étapes

Un autre cas avancé consiste à préserver les données d'un formulaire lors de la navigation dans un processus multi-étapes :

1function SpaceshipConfigStep1() {
2  const [config, setConfig] = useState({
3    name: '',
4    type: 'exploration',
5    crew: 4
6  });
7
8  const navigate = useNavigate();
9
10  const handleNext = () => {
11    // Validation du formulaire...
12
13    // Naviguer vers l'étape suivante avec les données
14    navigate('/config/step2', {
15      state: { config }
16    });
17  };
18
19  return (
20    <div className="config-form">
21      <h2>Configuration du vaisseau - Étape 1</h2>
22      <form onSubmit={e => { e.preventDefault(); handleNext(); }}>
23        <div className="form-field">
24          <label htmlFor="name">Nom du vaisseau :</label>
25          <input
26            id="name"
27            value={config.name}
28            onChange={e => setConfig({...config, name: e.target.value})}
29            required
30          />
31        </div>
32
33        <div className="form-field">
34          <label htmlFor="type">Type de mission :</label>
35          <select
36            id="type"
37            value={config.type}
38            onChange={e => setConfig({...config, type: e.target.value})}
39          >
40            <option value="exploration">Exploration</option>
41            <option value="science">Recherche scientifique</option>
42            <option value="mining">Extraction de ressources</option>
43          </select>
44        </div>
45
46        <div className="form-field">
47          <label htmlFor="crew">Taille de l'équipage :</label>
48          <input
49            id="crew"
50            type="number"
51            min="1"
52            max="10"
53            value={config.crew}
54            onChange={e => setConfig({...config, crew: parseInt(e.target.value)})}
55            required
56          />
57        </div>
58
59        <button type="submit">Suivant</button>
60      </form>
61    </div>
62  );
63}
64
65function SpaceshipConfigStep2() {
66  const location = useLocation();
67  const navigate = useNavigate();
68
69  // Obtenir la configuration de l'étape précédente
70  const { config: prevConfig } = location.state || { config: null };
71
72  // Si nous n'avons pas de configuration, rediriger en arrière
73  useEffect(() => {
74    if (!prevConfig) {
75      navigate('/config/step1', { replace: true });
76    }
77  }, [prevConfig, navigate]);
78
79  // Initialiser l'état avec les données de l'étape précédente
80  const [config, setConfig] = useState({
81    ...prevConfig,
82    engines: 'ion',
83    fuelCapacity: 5000,
84    lifeSupport: 'advanced'
85  });
86
87  const handleFinish = () => {
88    // Configuration complète des deux étapes
89    const finalConfig = {
90      ...config
91    };
92
93    // Finaliser la configuration
94    navigate('/config/summary', {
95      state: { finalConfig }
96    });
97  };
98
99  if (!prevConfig) return null; // Ne rien rendre tant que la redirection ne s'exécute pas
100
101  return (
102    <div className="config-form">
103      <h2>Configuration du vaisseau - Étape 2</h2>
104      <form onSubmit={e => { e.preventDefault(); handleFinish(); }}>
105        {/* Deuxième série de champs du formulaire */}
106
107        <div className="form-actions">
108          <button
109            type="button"
110            onClick={() => navigate(-1)}
111          >
112            Retour
113          </button>
114          <button type="submit">Terminer</button>
115        </div>
116      </form>
117    </div>
118  );
119}

La fonction replace et l'historique du navigateur

Dans les exemples, nous avons utilisé le paramètre replace: true. C'est une option importante qui affecte le comportement de l'historique du navigateur :

  • Lorsque replace: false (par défaut) - ajoute une nouvelle page à l'historique. L'utilisateur peut revenir à la page précédente avec le bouton "Retour".
  • Lorsque replace: true - remplace la page actuelle dans l'historique. Lorsque l'utilisateur appuie sur le bouton "Retour", il revient à la page d'avant la dernière redirection.
1// Ajoute une nouvelle page à l'historique du navigateur
2navigate('/dashboard', { state: { data: 'example' } });
3
4// Remplace la page actuelle dans l'historique
5navigate('/dashboard', {
6  state: { data: 'example' },
7  replace: true
8});

L'option replace est particulièrement utile dans les processus de connexion, où après avoir redirigé l'utilisateur vers la page de connexion puis de retour, nous ne voulons pas qu'il puisse revenir à la page de connexion à l'aide du bouton "Retour".

Limites et alternatives

Le location state a certaines limites :

  1. Non persistant - les données sont perdues lors d'un rafraîchissement de la page
  2. Taille limitée - nous ne devrions pas passer de très gros objets
  3. Non visible dans l'URL - ce qui peut être un avantage (confidentialité) ou un inconvénient (impossible de mémoriser/partager l'état exact)

Méthodes alternatives pour passer des données entre les pages :

  1. Paramètres d'URL : les données sont visibles dans l'adresse URL

    1// Passage via l'URL
    2navigate(`/planet/mars?atmosphere=thin&temperature=-60`);
    3
    4// Lecture
    5const { search } = useLocation();
    6const queryParams = new URLSearchParams(search);
    7const atmosphere = queryParams.get('atmosphere'); // 'thin'
  2. État global de l'application : gestion d'état avec Context API, Redux, Zustand, etc.

    1// Enregistrement des données
    2dispatch(setCurrentPlanet({ name: 'Mars', atmosphere: 'thin' }));
    3navigate('/planet-details');
    4
    5// Lecture des données
    6const currentPlanet = useSelector(state => state.planets.current);
  3. Stockage local : sessionStorage, localStorage

    1// Enregistrement des données
    2sessionStorage.setItem('planetData', JSON.stringify({ name: 'Mars' }));
    3navigate('/planet-details');
    4
    5// Lecture des données
    6const planetData = JSON.parse(sessionStorage.getItem('planetData') || '{}');

Bonnes pratiques

  1. Portée minimale des données : ne passe que les données nécessaires
  2. Validation des données : vérifie toujours que les données existent avant de les utiliser
  3. Prévoir un fallback : aie un plan B lorsque les données ne sont pas disponibles
  4. Éviter les données sensibles : ne passe pas d'informations confidentielles via le location state
  5. Utiliser la sécurité de typage : si tu utilises TypeScript, définis des types pour les données dans le location state

Résumé

Passer un état à travers les emplacements dans React Router est comme une communication intergalactique - nous pouvons envoyer des données importantes entre différentes "stations" (pages) de notre application sans avoir à les stocker globalement.

Cette technique est particulièrement utile dans des cas tels que :

  • Passer les détails d'un objet vers une page de détails
  • Mémoriser l'emplacement d'origine lors des redirections d'authentification
  • Préserver l'état d'un formulaire dans des processus multi-étapes
  • Passer des messages et des notifications entre les pages (par ex. "Opération terminée avec succès")

Lorsque tu choisis entre le location state et d'autres méthodes de passage de données, prends toujours en compte la persistance, la visibilité et la taille des données pour choisir la meilleure approche selon le cas d'usage spécifique.

Code de cette leçon : App.jsx
1import React from 'react';
2import { BrowserRouter, Routes, Route, Link, Navigate, useNavigate } from 'react-router-dom';
3
4// Page 404
5function NotFoundPage() {
6  const navigate = useNavigate();
7
8  return (
9    <div className="page not-found">
10      <div className="error-code">404</div>
11      <h1>Perdu dans l'espace</h1>
12      <p>Cette page n'existe pas dans notre univers.</p>
13      <div className="actions">
14        <button onClick={() => navigate('/')}>Aller à l'accueil</button>
15        <button onClick={() => navigate(-1)}>← Retour</button>
16      </div>
17    </div>
18  );
19}
20
21// Page d'accueil
22function HomePage() {
23  return (
24    <div className="page">
25      <h1>Page d'accueil</h1>
26      <p>Bienvenue au centre de contrôle !</p>
27    </div>
28  );
29}
30
31// Page de profil (redirection depuis l'ancienne URL)
32function ProfilePage() {
33  return (
34    <div className="page">
35      <h1>Profil utilisateur</h1>
36      <p>Tes données de profil.</p>
37    </div>
38  );
39}
40
41// Page des paramètres
42function SettingsPage() {
43  return (
44    <div className="page">
45      <h1>Paramètres</h1>
46      <p>Configuration du système.</p>
47    </div>
48  );
49}
50
51// Ancienne page (redirection)
52function OldPage() {
53  return <Navigate to="/new-page" replace />;
54}
55
56function NewPage() {
57  return (
58    <div className="page">
59      <h1>Nouvelle page</h1>
60      <p>Tu as été redirigé depuis l'ancienne page !</p>
61    </div>
62  );
63}
64
65function Navigation() {
66  return (
67    <nav>
68      <Link to="/">Accueil</Link>
69      <Link to="/profile">Profil</Link>
70      <Link to="/settings">Paramètres</Link>
71      <Link to="/old-page">Ancienne page (redirect)</Link>
72      <Link to="/non-existent">Inexistante</Link>
73    </nav>
74  );
75}
76
77function App() {
78  return (
79    <BrowserRouter>
80      <div className="app">
81        <Navigation />
82        <Routes>
83          <Route path="/" element={<HomePage />} />
84          <Route path="/profile" element={<ProfilePage />} />
85          <Route path="/settings" element={<SettingsPage />} />
86
87          {/* Redirection de l'ancienne URL vers la nouvelle */}
88          <Route path="/old-page" element={<Navigate to="/new-page" replace />} />
89          <Route path="/new-page" element={<NewPage />} />
90
91          {/* Redirection de /user vers /profile */}
92          <Route path="/user" element={<Navigate to="/profile" />} />
93          <Route path="/user/:id" element={<Navigate to="/profile" />} />
94
95          {/* Route catch-all - 404 */}
96          <Route path="*" element={<NotFoundPage />} />
97        </Routes>
98      </div>
99    </BrowserRouter>
100  );
101}
102
103export 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 peux-tu passer des données à la page suivante à l'aide du composant Link ?

  2. 2. Quel hook utiliserais-tu pour lire les données passées via le location state ?

Tâches pratiques dans le jeu

  • Clique dans l'ordre

    Ordonne la syntaxe pour passer un état de manière programmatique à l'aide de navigate :

Articles utiles