Cours JavaScript et React · Module 3: État et événements
Projet : tableau de bord de la colonie martienne
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 :
- Panneau des systèmes de la colonie - des moniteurs pour l'atmosphère, l'énergie, l'eau et la température
- Centre de contrôle - des boutons pour ajuster manuellement les paramètres
- Formulaire d'inscription des astronautes - un formulaire contrôlé pour ajouter des membres d'équipage
- Journal des événements - un journal que chaque panneau alimente avec la fonction
addLogEntryreç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
- useState - minimum 4 états (atmosphère, énergie, eau, température) + l'état de l'équipage + l'état du formulaire
- Gestion des événements - des boutons ajustant les paramètres avec des garde-fous (min/max)
- Formulaire contrôlé - ajout de nouveaux astronautes avec validation
- Alarmes - vérification de l'état critique dans les gestionnaires et écriture dans le journal
- Faire remonter l'état - un journal d'événements partagé accessible depuis plusieurs composants
- 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 ?