Cours JavaScript et React · Module 3: État et événements

Projet : tableau de bord de la colonie martienne

5 min de lecture
Dans cette leçon5

Félicitations - tu as atteint le projet final du module « L'état et les événements » ! Dans les leçons précédentes, tu as appris useState, la gestion des événements, le flux de données, la remontée de l'état (lifting state up) et les formulaires contrôlés. Maintenant, tu vas combiner toutes ces connaissances en une seule application cohérente.

Ta mission est de construire un tableau de bord de la colonie martienne - un panneau interactif pour gérer une base sur Mars. Ra, ton guide cosmique, a recueilli les exigences auprès du quartier général. Mission : écrire une application React qui surveille et contrôle les systèmes clés de la colonie.

Ce que tu vas construire

Le tableau de bord se compose de quatre sections connectées :

  1. Panneau des systèmes de la colonie - des moniteurs pour l'atmosphère, l'énergie, l'eau et la température
  2. Centre de contrôle - des boutons pour ajuster manuellement les paramètres
  3. Formulaire d'inscription des astronautes - un formulaire contrôlé pour ajouter des membres d'équipage
  4. Journal des événements - un journal que chaque panneau alimente avec la fonction addLogEntry reçue via les props

Toutes les sections doivent fonctionner ensemble - les changements dans un panneau affectent les autres.

Exigences techniques

1. Plusieurs états (useState)

Chaque système de la colonie a son propre état :

1const [atmosphere, setAtmosphere] = useState(78);
2const [power, setPower] = useState(85);
3const [water, setWater] = useState(62);
4const [temperature, setTemperature] = useState(-10);
5const [crew, setCrew] = useState([
6  { id: 1, name: 'Anna Kowalska', role: 'Commandante' },
7  { id: 2, name: 'Jan Wisniewski', role: 'Ingénieur' }
8]);

2. Gestion des événements

Chaque système a des boutons d'ajustement. Souviens-toi des plages sûres :

1const handleAtmosphereBoost = () => {
2  setAtmosphere(prev => Math.min(100, prev + 5));
3  addLogEntry("Niveau d'O2 augmenté de 5 %");
4};

3. Formulaire d'inscription contrôlé

Un formulaire pour ajouter de nouveaux astronautes à l'équipage :

1const [newCrew, setNewCrew] = useState({ name: '', role: '' });
2
3const handleCrewChange = (event) => {
4  const { name, value } = event.target;
5  setNewCrew(prev => ({ ...prev, [name]: value }));
6};
7
8const handleCrewSubmit = (event) => {
9  event.preventDefault();
10  if (!newCrew.name.trim()) return;
11  setCrew(prev => [...prev, { id: Date.now(), ...newCrew }]);
12  setNewCrew({ name: '', role: '' });
13};

4. Les alarmes dans les gestionnaires d'événements

Vérifie l'état critique dans le gestionnaire qui modifie la valeur - l'alarme apparaît exactement au moment du changement :

1const changePower = (delta) => {
2  const next = Math.min(100, Math.max(0, power + delta));
3  setPower(next);
4  if (next < 20) {
5    addLogEntry('ALARME : énergie à un niveau critique !');
6  }
7};

Les changements dans le temps (par ex. la consommation d'eau toutes les 5 secondes) nécessitent le hook useEffect avec un nettoyage - tu le découvriras dans le module sur les effets et tu pourras alors enrichir ce tableau de bord.

5. Faire remonter l'état - journal d'événements partagé

L'état du journal et la fonction addLogEntry vivent dans le composant principal et sont transmis vers le bas :

1function MarsColonyDashboard() {
2  const [log, setLog] = useState([]);
3
4  const addLogEntry = (message) => {
5    setLog(prev => [
6      { id: Date.now(), time: new Date().toLocaleTimeString(), message },
7      ...prev
8    ].slice(0, 20));
9  };
10
11  return (
12    <div>
13      <SystemsPanel onLog={addLogEntry} />
14      <CrewPanel onLog={addLogEntry} />
15      <EventLog entries={log} />
16    </div>
17  );
18}

Structure des composants

Voici à quoi peut ressembler l'arbre des composants - l'état vit tout en haut, et les panneaux le reçoivent via les props :

1MarsColonyDashboard (état principal)
2├── SystemsPanel (props : valeurs + callbacks)
3│   └── SystemGauge × 4
4├── CrewPanel (props : crew, formulaire, callbacks)
5│   ├── CrewList
6│   └── CrewForm
7└── EventLog (props : entries)

Les données circulent vers le bas à travers les props, les événements remontent vers le haut à travers les callbacks - le cœur du flux de données unidirectionnel en React !

Critères d'évaluation

  1. useState - minimum 4 états (atmosphère, énergie, eau, température) + l'état de l'équipage + l'état du formulaire
  2. Gestion des événements - des boutons ajustant les paramètres avec des garde-fous (min/max)
  3. Formulaire contrôlé - ajout de nouveaux astronautes avec validation
  4. Alarmes - vérification de l'état critique dans les gestionnaires et écriture dans le journal
  5. Faire remonter l'état - un journal d'événements partagé accessible depuis plusieurs composants
  6. Découpage en composants - minimum 3 composants distincts communiquant via les props

Le squelette de départ

Pour démarrer, tu reçois un squelette d'application qui suit la structure ci-dessus : MarsColonyDashboard détient les états et le journal, et les panneaux reçoivent les données et les fonctions via les props. Les boutons de l'atmosphère et de l'énergie fonctionnent déjà et ajoutent des entrées au journal - complète les endroits marqués TODO : les états de l'eau et de la température, les limites 0-100, l'alarme d'énergie faible et le formulaire d'inscription des astronautes.

Code de cette leçon : App.jsx
1import { useState } from 'react';
2
3// Squelette du projet "Tableau de bord de la colonie martienne" (leçon 3_7).
4// Les états et le journal vivent dans MarsColonyDashboard, et les panneaux reçoivent les données et les fonctions via les props.
5// Complète les endroits marqués TODO.
6
7let nextLogId = 1;
8
9function SystemGauge({ label, value, unit, onAdjust }) {
10  return (
11    <div style={styles.gauge}>
12      <h3 style={styles.gaugeLabel}>{label}</h3>
13      <p style={styles.gaugeValue}>{value}{unit}</p>
14      <button style={styles.button} onClick={() => onAdjust(-5)}>-5</button>
15      <button style={styles.button} onClick={() => onAdjust(5)}>+5</button>
16    </div>
17  );
18}
19
20function SystemsPanel({ atmosphere, power, onAtmosphereChange, onPowerChange }) {
21  return (
22    <section style={styles.panel}>
23      <h2 style={styles.title}>Systèmes de la colonie</h2>
24      <div style={styles.gauges}>
25        <SystemGauge label="Atmosphère (O2)" value={atmosphere} unit="%" onAdjust={onAtmosphereChange} />
26        <SystemGauge label="Énergie" value={power} unit="%" onAdjust={onPowerChange} />
27        {/* TODO : jauges de l'eau (%) et de la température (°C) */}
28      </div>
29    </section>
30  );
31}
32
33function CrewPanel({ crew }) {
34  // TODO : un formulaire contrôlé { name, role } avec e.preventDefault() et une validation.
35  // Une fonction reçue via les props ajoute le nouvel astronaute, car l'état de l'équipage vit dans le parent.
36  return (
37    <section style={styles.panel}>
38      <h2 style={styles.title}>Équipage ({crew.length})</h2>
39      <ul style={styles.list}>
40        {crew.map((member) => (
41          <li key={member.id}>{member.name} - {member.role}</li>
42        ))}
43      </ul>
44    </section>
45  );
46}
47
48function EventLog({ entries }) {
49  return (
50    <section style={styles.panel}>
51      <h2 style={styles.title}>Journal des événements</h2>
52      {entries.length === 0 ? (
53        <p style={styles.muted}>Aucune entrée pour l'instant - utilise les boutons du panneau des systèmes.</p>
54      ) : (
55        <ul style={styles.list}>
56          {entries.map((entry) => (
57            <li key={entry.id}>[{entry.time}] {entry.message}</li>
58          ))}
59        </ul>
60      )}
61    </section>
62  );
63}
64
65function MarsColonyDashboard() {
66  const [atmosphere, setAtmosphere] = useState(78);
67  const [power, setPower] = useState(85);
68  // TODO : les états water (62) et temperature (-10)
69  const [crew, setCrew] = useState([
70    { id: 1, name: 'Anna Kowalska', role: 'Commandante' },
71    { id: 2, name: 'Jan Wisniewski', role: 'Ingénieur' },
72  ]);
73  const [log, setLog] = useState([]);
74
75  // Journal partagé : les panneaux y ajoutent des entrées avec cette fonction transmise via les props
76  const addLogEntry = (message) => {
77    const entry = { id: nextLogId++, time: new Date().toLocaleTimeString(), message };
78    setLog((prev) => [entry, ...prev].slice(0, 20));
79  };
80
81  const changeAtmosphere = (delta) => {
82    // TODO : garde la valeur entre 0 et 100 (Math.min et Math.max)
83    setAtmosphere(atmosphere + delta);
84    addLogEntry('Atmosphère : ' + (delta > 0 ? '+' : '') + delta + '%');
85  };
86
87  const changePower = (delta) => {
88    // TODO : la plage 0-100 et une alarme dans le journal quand l'énergie passe sous 20 %
89    setPower(power + delta);
90    addLogEntry('Énergie : ' + (delta > 0 ? '+' : '') + delta + '%');
91  };
92
93  // TODO : une fonction addCrewMember(member) qui ajoute l'astronaute avec setCrew
94  // et écrit une entrée dans le journal ; transmets-la à CrewPanel
95
96  return (
97    <div style={styles.app}>
98      <h1 style={styles.header}>Tableau de bord de la colonie martienne</h1>
99      <SystemsPanel
100        atmosphere={atmosphere}
101        power={power}
102        onAtmosphereChange={changeAtmosphere}
103        onPowerChange={changePower}
104      />
105      <CrewPanel crew={crew} />
106      <EventLog entries={log} />
107    </div>
108  );
109}
110
111const styles = {
112  app: { fontFamily: 'Arial, sans-serif', background: '#0b1020', color: '#e6ecff', minHeight: '100vh', padding: '20px' },
113  header: { color: '#ff8c42', textAlign: 'center', marginTop: 0 },
114  panel: { background: '#151c33', borderRadius: '12px', padding: '16px', marginBottom: '16px' },
115  title: { marginTop: 0, color: '#7fd1ff' },
116  gauges: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(160px, 1fr))', gap: '12px' },
117  gauge: { background: '#1e2747', borderRadius: '10px', padding: '12px', textAlign: 'center' },
118  gaugeLabel: { margin: '0 0 6px', fontSize: '15px' },
119  gaugeValue: { margin: '0 0 10px', fontSize: '28px', fontWeight: 'bold' },
120  button: { margin: '0 4px', padding: '6px 12px', border: 'none', borderRadius: '6px', background: '#3a4f9a', color: 'white', cursor: 'pointer' },
121  list: { margin: 0, paddingLeft: '20px', lineHeight: 1.7 },
122  muted: { color: '#8f9bbf', margin: 0 },
123};
124
125export default function App() {
126  return <MarsColonyDashboard />;
127}

Bonne chance, pilote ! Ce projet est ta première vraie mission dans le monde de React - montre que tu sais gérer une colonie sur Mars. Souviens-toi : chaque panneau est un module distinct du vaisseau, mais les données et les décisions passent par un seul commandant - le composant principal.

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

Articles utiles