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

Les formulaires en React - les composants contrôlés

7 min de lecture
Dans cette leçon6

À bord du vaisseau spatial React, les formulaires sont comme des panneaux de configuration - ils servent à saisir les données de navigation, à régler les paramètres de mission et à communiquer avec le quartier général. Dans cette leçon, tu vas découvrir les composants contrôlés - un pattern où React prend le contrôle total des données du formulaire, tout comme l'ordinateur de bord contrôle chaque système du vaisseau.

Composants contrôlés vs non contrôlés

En React, on distingue deux approches de la gestion des formulaires :

Les composants contrôlés - React gère la valeur de l'input via l'état. Chaque changement passe par useState et onChange :

1function ControlledInput() {
2  const [name, setName] = useState("");
3
4  return (
5    <input
6      type="text"
7      value={name}
8      onChange={(e) => setName(e.target.value)}
9    />
10  );
11}

Les composants non contrôlés - le navigateur gère la valeur, et on la lit via ref :

1function UncontrolledInput() {
2  const inputRef = useRef(null);
3
4  const handleSubmit = (e) => {
5    e.preventDefault();
6    console.log(inputRef.current.value);
7  };
8
9  return (
10    <form onSubmit={handleSubmit}>
11      <input type="text" ref={inputRef} />
12      <button type="submit">Envoyer</button>
13    </form>
14  );
15}

Pourquoi les composants contrôlés sont-ils meilleurs ? Tout comme l'ordinateur de bord du vaisseau doit connaître à tout moment l'état de chaque système, les composants contrôlés donnent à React une visibilité totale sur les données du formulaire. Cela te permet de valider, formater et réagir aux changements en temps réel.

Gérer les champs texte avec useState

Un input texte simple

1function PilotRegistration() {
2  const [pilotName, setPilotName] = useState("");
3  const [callSign, setCallSign] = useState("");
4
5  return (
6    <div className="registration-form">
7      <h2>Inscription des pilotes</h2>
8
9      <label>Nom du pilote :</label>
10      <input
11        type="text"
12        value={pilotName}
13        onChange={(e) => setPilotName(e.target.value)}
14        placeholder="Saisis le nom..."
15      />
16
17      <label>Indicatif d'appel :</label>
18      <input
19        type="text"
20        value={callSign}
21        onChange={(e) => setCallSign(e.target.value.toUpperCase())}
22        placeholder="Par ex. ALPHA-7"
23      />
24
25      <p>Pilote : {pilotName} ({callSign})</p>
26    </div>
27  );
28}

Gérer plusieurs champs avec un seul état

Au lieu de créer un useState distinct pour chaque champ, tu peux utiliser un seul objet d'état - comme un registre central des données de mission :

1function MissionConfigForm() {
2  const [formData, setFormData] = useState({
3    missionName: "",
4    destination: "",
5    crewSize: "",
6    priority: "normal"
7  });
8
9  const handleChange = (event) => {
10    const { name, value } = event.target;
11    setFormData((prev) => ({
12      ...prev,
13      [name]: value
14    }));
15  };
16
17  return (
18    <div className="mission-config">
19      <h2>Configuration de la mission</h2>
20
21      <label>Nom de la mission :</label>
22      <input
23        type="text"
24        name="missionName"
25        value={formData.missionName}
26        onChange={handleChange}
27      />
28
29      <label>Destination :</label>
30      <input
31        type="text"
32        name="destination"
33        value={formData.destination}
34        onChange={handleChange}
35      />
36
37      <label>Taille de l'équipage :</label>
38      <input
39        type="number"
40        name="crewSize"
41        value={formData.crewSize}
42        onChange={handleChange}
43      />
44
45      <label>Priorité :</label>
46      <input
47        type="text"
48        name="priority"
49        value={formData.priority}
50        onChange={handleChange}
51      />
52
53      <pre>{JSON.stringify(formData, null, 2)}</pre>
54    </div>
55  );
56}

L'astuce clé : l'attribut name des inputs correspond aux clés de l'objet d'état, et [name]: value (computed property name) met à jour dynamiquement le champ correspondant.

Les cases à cocher et les éléments select

Case à cocher - les interrupteurs de systèmes

Les cases à cocher en React utilisent checked au lieu de value :

1function ShipSystemsPanel() {
2  const [systems, setSystems] = useState({
3    engines: true,
4    shields: false,
5    weapons: false,
6    lifeSupport: true
7  });
8
9  const handleToggle = (event) => {
10    const { name, checked } = event.target;
11    setSystems((prev) => ({
12      ...prev,
13      [name]: checked
14    }));
15  };
16
17  return (
18    <div className="systems-panel">
19      <h2>Systèmes du vaisseau</h2>
20
21      <label>
22        <input
23          type="checkbox"
24          name="engines"
25          checked={systems.engines}
26          onChange={handleToggle}
27        />
28        Moteurs
29      </label>
30
31      <label>
32        <input
33          type="checkbox"
34          name="shields"
35          checked={systems.shields}
36          onChange={handleToggle}
37        />
38        Boucliers
39      </label>
40
41      <label>
42        <input
43          type="checkbox"
44          name="weapons"
45          checked={systems.weapons}
46          onChange={handleToggle}
47        />
48        Systèmes d'armement
49      </label>
50
51      <label>
52        <input
53          type="checkbox"
54          name="lifeSupport"
55          checked={systems.lifeSupport}
56          onChange={handleToggle}
57        />
58        Système de survie
59      </label>
60
61      <div className="status">
62        Systèmes actifs : {Object.entries(systems)
63          .filter(([, active]) => active)
64          .map(([name]) => name)
65          .join(", ")}
66      </div>
67    </div>
68  );
69}

Select - choisir dans une liste

L'élément <select> en React fonctionne comme les autres inputs contrôlés - on contrôle la valeur via value et onChange :

1function NavigationPanel() {
2  const [destination, setDestination] = useState("");
3  const [warpSpeed, setWarpSpeed] = useState("1");
4
5  const destinations = [
6    { value: "mars", label: "Mars" },
7    { value: "jupiter", label: "Jupiter" },
8    { value: "saturn", label: "Saturne" },
9    { value: "alpha-centauri", label: "Alpha du Centaure" },
10    { value: "andromeda", label: "Galaxie d'Andromède" }
11  ];
12
13  return (
14    <div className="nav-panel">
15      <h2>Panneau de navigation</h2>
16
17      <label>Destination :</label>
18      <select
19        value={destination}
20        onChange={(e) => setDestination(e.target.value)}
21      >
22        <option value="">-- Choisis une destination --</option>
23        {destinations.map((dest) => (
24          <option key={dest.value} value={dest.value}>
25            {dest.label}
26          </option>
27        ))}
28      </select>
29
30      <label>Vitesse warp :</label>
31      <select
32        value={warpSpeed}
33        onChange={(e) => setWarpSpeed(e.target.value)}
34      >
35        <option value="1">Warp 1</option>
36        <option value="3">Warp 3</option>
37        <option value="5">Warp 5</option>
38        <option value="9">Warp 9 (maximum)</option>
39      </select>
40
41      {destination && (
42        <p>Cap fixé : {destination} à Warp {warpSpeed}</p>
43      )}
44    </div>
45  );
46}

Gérer l'envoi d'un formulaire

Envoyer un formulaire, c'est comme émettre un ordre depuis le quartier général - cela doit être contrôlé et sécurisé :

1function MissionReportForm() {
2  const [formData, setFormData] = useState({
3    missionName: "",
4    commander: "",
5    status: "in-progress",
6    description: "",
7    isClassified: false
8  });
9  const [submitted, setSubmitted] = useState(false);
10
11  const handleChange = (event) => {
12    const { name, value, type, checked } = event.target;
13    setFormData((prev) => ({
14      ...prev,
15      [name]: type === "checkbox" ? checked : value
16    }));
17  };
18
19  const handleSubmit = (event) => {
20    event.preventDefault();
21
22    // Validation
23    if (!formData.missionName.trim() || !formData.commander.trim()) {
24      alert("Remplis tous les champs obligatoires !");
25      return;
26    }
27
28    console.log("Rapport de mission :", formData);
29    setSubmitted(true);
30  };
31
32  const handleReset = () => {
33    setFormData({
34      missionName: "",
35      commander: "",
36      status: "in-progress",
37      description: "",
38      isClassified: false
39    });
40    setSubmitted(false);
41  };
42
43  if (submitted) {
44    return (
45      <div className="success">
46        <h2>Rapport envoyé !</h2>
47        <p>Mission : {formData.missionName}</p>
48        <button onClick={handleReset}>Nouveau rapport</button>
49      </div>
50    );
51  }
52
53  return (
54    <form onSubmit={handleSubmit} className="mission-report">
55      <h2>Rapport de mission</h2>
56
57      <label>Nom de la mission * :</label>
58      <input
59        type="text"
60        name="missionName"
61        value={formData.missionName}
62        onChange={handleChange}
63        required
64      />
65
66      <label>Commandant * :</label>
67      <input
68        type="text"
69        name="commander"
70        value={formData.commander}
71        onChange={handleChange}
72        required
73      />
74
75      <label>Statut :</label>
76      <select name="status" value={formData.status} onChange={handleChange}>
77        <option value="in-progress">En cours</option>
78        <option value="completed">Terminée</option>
79        <option value="failed">Échec</option>
80      </select>
81
82      <label>Description :</label>
83      <textarea
84        name="description"
85        value={formData.description}
86        onChange={handleChange}
87        rows={4}
88        placeholder="Décris le déroulement de la mission..."
89      />
90
91      <label>
92        <input
93          type="checkbox"
94          name="isClassified"
95          checked={formData.isClassified}
96          onChange={handleChange}
97        />
98        Rapport confidentiel
99      </label>
100
101      <div className="form-actions">
102        <button type="submit">Envoyer le rapport</button>
103        <button type="button" onClick={handleReset}>Effacer</button>
104      </div>
105    </form>
106  );
107}

Faire remonter l'état (lifting state up)

Quand plusieurs composants doivent partager les données d'un formulaire, on déplace l'état vers leur parent commun - comme un centre de commandement qui coordonne les données de plusieurs panneaux :

1function TemperatureInput({ label, value, onChange }) {
2  return (
3    <div>
4      <label>{label}:</label>
5      <input
6        type="number"
7        value={value}
8        onChange={(e) => onChange(e.target.value)}
9      />
10    </div>
11  );
12}
13
14function TemperatureConverter() {
15  // Le parent ne retient que la dernière saisie et son échelle
16  const [temperature, setTemperature] = useState({ scale: "c", value: "" });
17
18  const convert = (value, formula) => {
19    const number = parseFloat(value);
20    return Number.isNaN(number) ? "" : String(Math.round(formula(number) * 100) / 100);
21  };
22
23  const celsius = temperature.scale === "f"
24    ? convert(temperature.value, (f) => ((f - 32) * 5) / 9)
25    : temperature.value;
26  const fahrenheit = temperature.scale === "c"
27    ? convert(temperature.value, (c) => (c * 9) / 5 + 32)
28    : temperature.value;
29
30  return (
31    <div className="converter">
32      <h2>Convertisseur de température de la coque</h2>
33      <TemperatureInput
34        label="Celsius"
35        value={celsius}
36        onChange={(value) => setTemperature({ scale: "c", value })}
37      />
38      <TemperatureInput
39        label="Fahrenheit"
40        value={fahrenheit}
41        onChange={(value) => setTemperature({ scale: "f", value })}
42      />
43    </div>
44  );
45}

Dans ce pattern :

  • L'état (temperature : la dernière valeur saisie et son échelle) vit dans le parent (TemperatureConverter)
  • Les composants enfants (TemperatureInput) reçoivent la valeur via les props
  • Les changements sont transmis vers le haut grâce au callback onChange
  • Le parent recalcule et synchronise les deux valeurs

Le parent ne stocke pas des degrés Celsius déjà calculés, mais la dernière valeur saisie et son échelle. Ainsi, le champ dans lequel tu tapes affiche exactement ce que tu saisis (même un - seul ou 10), et l'autre champ est converti et arrondi à deux décimales.

Résumé

Les formulaires en React - principes clés :

  1. Composants contrôlés - React gère les valeurs des inputs via value + onChange
  2. Un seul état pour plusieurs champs - utilise un objet d'état avec [name]: value dynamique
  3. Cases à cocher - utilise checked au lieu de value, lis event.target.checked
  4. Select - contrôle via value sur <select>, et non sur <option>
  5. preventDefault() - bloque toujours l'envoi par défaut du formulaire
  6. Validation - vérifie les données dans le gestionnaire onSubmit avant l'envoi
  7. Faire remonter l'état (lifting state up) - déplace l'état vers un parent commun quand les composants doivent partager des données
Code de cette leçon : App.jsx
1import React, { useState } from 'react';
2
3function SpaceMissionForm() {
4  const [formData, setFormData] = useState({
5    missionName: '',
6    destination: 'mars',
7    crewSize: 4,
8    duration: '',
9    priority: 'medium',
10    equipment: [],
11    notes: '',
12    launchDate: '',
13    isUrgent: false
14  });
15
16  const [errors, setErrors] = useState({});
17  const [submitted, setSubmitted] = useState(false);
18
19  const equipmentOptions = [
20    { id: 'rover', label: 'Rover' },
21    { id: 'drone', label: 'Drone' },
22    { id: 'satellite', label: 'Satellite' },
23    { id: 'telescope', label: 'Télescope' },
24    { id: 'lab', label: 'Laboratoire' }
25  ];
26
27  const handleChange = (e) => {
28    const { name, value, type, checked } = e.target;
29    setFormData(prev => ({
30      ...prev,
31      [name]: type === 'checkbox' ? checked : value
32    }));
33    if (errors[name]) {
34      setErrors(prev => ({ ...prev, [name]: null }));
35    }
36  };
37
38  const handleEquipmentChange = (id) => {
39    setFormData(prev => ({
40      ...prev,
41      equipment: prev.equipment.includes(id)
42        ? prev.equipment.filter(e => e !== id)
43        : [...prev.equipment, id]
44    }));
45  };
46
47  const validate = () => {
48    const newErrors = {};
49    if (!formData.missionName.trim()) newErrors.missionName = 'Le nom de la mission est requis';
50    if (!formData.duration || formData.duration < 1) newErrors.duration = 'La durée doit être supérieure à 0';
51    if (!formData.launchDate) newErrors.launchDate = 'La date de lancement est requise';
52    if (formData.equipment.length === 0) newErrors.equipment = 'Choisis au moins un équipement';
53    return newErrors;
54  };
55
56  const handleSubmit = (e) => {
57    e.preventDefault();
58    const newErrors = validate();
59    if (Object.keys(newErrors).length > 0) {
60      setErrors(newErrors);
61      return;
62    }
63    setSubmitted(true);
64    console.log('Submitted:', formData);
65  };
66
67  const handleReset = () => {
68    setFormData({
69      missionName: '', destination: 'mars', crewSize: 4, duration: '',
70      priority: 'medium', equipment: [], notes: '', launchDate: '', isUrgent: false
71    });
72    setErrors({});
73    setSubmitted(false);
74  };
75
76  if (submitted) {
77    return (
78      <div style={{ fontFamily: 'Arial', padding: '40px', textAlign: 'center' }}>
79        <div style={{ fontSize: '64px', marginBottom: '20px' }}>▲</div>
80        <h1 style={{ color: '#4caf50' }}>Mission planifiée !</h1>
81        <div style={{ backgroundColor: '#f5f5f5', padding: '20px', borderRadius: '12px', maxWidth: '500px', margin: '20px auto', textAlign: 'left' }}>
82          <p><strong>Nom :</strong> {formData.missionName}</p>
83          <p><strong>Cible :</strong> {formData.destination}</p>
84          <p><strong>Équipage :</strong> {formData.crewSize} personnes</p>
85          <p><strong>Durée :</strong> {formData.duration} jours</p>
86          <p><strong>Date de lancement :</strong> {formData.launchDate}</p>
87          <p><strong>Équipement :</strong> {formData.equipment.join(', ')}</p>
88        </div>
89        <button onClick={handleReset} style={{ padding: '12px 24px', backgroundColor: '#667eea', color: 'white', border: 'none', borderRadius: '8px', cursor: 'pointer' }}>
90          Planifier une nouvelle mission
91        </button>
92      </div>
93    );
94  }
95
96  return (
97    <div style={{ fontFamily: 'Arial', padding: '20px', maxWidth: '600px', margin: '0 auto' }}>
98      <h1 style={{ textAlign: 'center', color: '#667eea' }}>Planification de mission spatiale</h1>
99
100      <form onSubmit={handleSubmit} style={{ display: 'flex', flexDirection: 'column', gap: '20px' }}>
101        <div>
102          <label style={{ display: 'block', marginBottom: '5px', fontWeight: 'bold' }}>Nom de la mission *</label>
103          <input type="text" name="missionName" value={formData.missionName} onChange={handleChange}
104            style={{ width: '100%', padding: '10px', borderRadius: '8px', border: errors.missionName ? '2px solid #f44336' : '1px solid #ddd', boxSizing: 'border-box' }}
105          />
106          {errors.missionName && <span style={{ color: '#f44336', fontSize: '12px' }}>{errors.missionName}</span>}
107        </div>
108
109        <div>
110          <label style={{ display: 'block', marginBottom: '5px', fontWeight: 'bold' }}>Destination</label>
111          <select name="destination" value={formData.destination} onChange={handleChange}
112            style={{ width: '100%', padding: '10px', borderRadius: '8px', border: '1px solid #ddd' }}>
113            <option value="mars">Mars</option>
114            <option value="moon">Lune</option>
115            <option value="venus">Vénus</option>
116            <option value="jupiter">Jupiter</option>
117            <option value="saturn">Saturne</option>
118          </select>
119        </div>
120
121        <div>
122          <label style={{ display: 'block', marginBottom: '5px', fontWeight: 'bold' }}>Taille de l'équipage : {formData.crewSize}</label>
123          <input type="range" name="crewSize" min="1" max="20" value={formData.crewSize} onChange={handleChange}
124            style={{ width: '100%' }}
125          />
126        </div>
127
128        <div>
129          <label style={{ display: 'block', marginBottom: '5px', fontWeight: 'bold' }}>Durée (jours) *</label>
130          <input type="number" name="duration" value={formData.duration} onChange={handleChange}
131            style={{ width: '100%', padding: '10px', borderRadius: '8px', border: errors.duration ? '2px solid #f44336' : '1px solid #ddd', boxSizing: 'border-box' }}
132          />
133          {errors.duration && <span style={{ color: '#f44336', fontSize: '12px' }}>{errors.duration}</span>}
134        </div>
135
136        <div>
137          <label style={{ display: 'block', marginBottom: '5px', fontWeight: 'bold' }}>Date de lancement *</label>
138          <input type="date" name="launchDate" value={formData.launchDate} onChange={handleChange}
139            style={{ width: '100%', padding: '10px', borderRadius: '8px', border: errors.launchDate ? '2px solid #f44336' : '1px solid #ddd', boxSizing: 'border-box' }}
140          />
141          {errors.launchDate && <span style={{ color: '#f44336', fontSize: '12px' }}>{errors.launchDate}</span>}
142        </div>
143
144        <div>
145          <label style={{ display: 'block', marginBottom: '10px', fontWeight: 'bold' }}>Priorité</label>
146          <div style={{ display: 'flex', gap: '20px' }}>
147            {['low', 'medium', 'high'].map(p => (
148              <label key={p} style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
149                <input type="radio" name="priority" value={p} checked={formData.priority === p} onChange={handleChange} />
150                {p === 'low' ? 'Basse' : p === 'medium' ? 'Moyenne' : 'Haute'}
151              </label>
152            ))}
153          </div>
154        </div>
155
156        <div>
157          <label style={{ display: 'block', marginBottom: '10px', fontWeight: 'bold' }}>Équipement *</label>
158          <div style={{ display: 'flex', flexWrap: 'wrap', gap: '10px' }}>
159            {equipmentOptions.map(eq => (
160              <label key={eq.id} style={{
161                padding: '8px 16px', borderRadius: '20px', cursor: 'pointer',
162                backgroundColor: formData.equipment.includes(eq.id) ? '#667eea' : '#e0e0e0',
163                color: formData.equipment.includes(eq.id) ? 'white' : '#333'
164              }}>
165                <input type="checkbox" checked={formData.equipment.includes(eq.id)}
166                  onChange={() => handleEquipmentChange(eq.id)} style={{ display: 'none' }}
167                />
168                {eq.label}
169              </label>
170            ))}
171          </div>
172          {errors.equipment && <span style={{ color: '#f44336', fontSize: '12px' }}>{errors.equipment}</span>}
173        </div>
174
175        <div>
176          <label style={{ display: 'flex', alignItems: 'center', gap: '10px', cursor: 'pointer' }}>
177            <input type="checkbox" name="isUrgent" checked={formData.isUrgent} onChange={handleChange} />
178            <span style={{ fontWeight: 'bold' }}>Mission urgente</span>
179          </label>
180        </div>
181
182        <div>
183          <label style={{ display: 'block', marginBottom: '5px', fontWeight: 'bold' }}>Notes</label>
184          <textarea name="notes" value={formData.notes} onChange={handleChange} rows="3"
185            style={{ width: '100%', padding: '10px', borderRadius: '8px', border: '1px solid #ddd', boxSizing: 'border-box', resize: 'vertical' }}
186          />
187        </div>
188
189        <div style={{ display: 'flex', gap: '10px' }}>
190          <button type="submit" style={{ flex: 1, padding: '12px', backgroundColor: '#4caf50', color: 'white', border: 'none', borderRadius: '8px', cursor: 'pointer', fontWeight: 'bold' }}>
191            Planifier la mission
192          </button>
193          <button type="button" onClick={handleReset} style={{ flex: 1, padding: '12px', backgroundColor: '#f44336', color: 'white', border: 'none', borderRadius: '8px', cursor: 'pointer', fontWeight: 'bold' }}>
194            Réinitialiser
195          </button>
196        </div>
197      </form>
198    </div>
199  );
200}
201
202export default function App() {
203  return <SpaceMissionForm />;
204}

Souviens-toi : Les composants contrôlés sont comme le système de contrôle du vaisseau - l'ordinateur de bord (React) connaît chaque changement en temps réel et peut réagir immédiatement !

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. Peut-on utiliser plusieurs hooks useState dans un même composant ?

  2. 2. Quelle est la différence entre l'état (state) et les props ?

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

Tâches pratiques dans le jeu

  • Clique dans l'ordre

    Construis un gestionnaire onSubmit qui empêche le rechargement de la page :

  • Tri vertical

    Remets dans l'ordre les étapes de création d'un formulaire contrôlé en React :

  • Éditeur de code

    Crée un composant EmergencyAlert avec un état message affiché dans un paragraphe <p> et affiche-le dans App. Ajoute un bouton ALARME : onClick définit le message « Alarme activée ! » et onMouseEnter le message « Attention : curseur sur le bouton d'alarme ». Ajoute aussi un champ <input> avec onKeyDown qui, à l'appui sur Entrée (e.key === 'Enter'), définit le message « Alarme déclenchée au clavier » et ignore les autres touches.

  • Éditeur de code

    Crée un parent ColonyControl avec un état powerLevel (useState(50)) et deux enfants sans état propre : PowerPlant({ powerLevel, onPowerChange }) affiche le niveau d'énergie et a des boutons +10 et -10 qui appellent onPowerChange avec la nouvelle valeur, et LifeSupport({ powerLevel }), qui affiche « Survie : active » quand powerLevel >= 30, et « ALARME : énergie insuffisante » sinon. ColonyControl transmet le même powerLevel aux deux enfants. Affiche ColonyControl dans App.

  • Éditeur de code

    Crée un composant ColonyDashboard avec quatre états useState séparés : temperature (par ex. -20), pressure (par ex. 600), oxygen (par ex. 21) et power (par ex. 80), et affiche-le dans App. Affiche les quatre valeurs et, pour chacune, ajoute un bouton qui ne modifie qu'elle (par ex. Chauffer +5, Pression +10, Oxygène +1, Énergie -10).

  • Clique dans l'ordre

    Construis l'expression qui récupère la valeur d'un champ input dans un gestionnaire d'événement :

  • Tri horizontal

    Remets dans l'ordre la syntaxe pour importer le hook useState depuis React :

  • Clique dans l'ordre

    Remets dans l'ordre le déroulement de la gestion d'un événement :

  • Tri vertical

    Remets dans l'ordre les façons de conserver l'état, de la plus simple à la plus élaborée, comme dans la leçon sur le flux de données :

  • Tri vertical

    Remets dans l'ordre ce qui se passe après la saisie d'un caractère dans le champ contrôlé <input value={name} onChange={(e) => setName(e.target.value)} /> :

  • Clique dans l'ordre

    Remets dans l'ordre le processus de mise à jour de l'état :

  • Tri horizontal

    Remets dans l'ordre l'attribut onChange correct pour un input contrôlé :

  • Clique dans l'ordre

    Clique sur les éléments dans l'ordre pour créer un input contrôlé avec onChange :

  • Tri vertical

    Remets dans l'ordre les étapes du cycle de mise à jour de l'état en React, de l'événement à la mise à jour du DOM :

  • Tri horizontal

    Remets dans l'ordre la bonne façon de mettre à jour une seule propriété d'un objet dans useState :

  • Tri vertical

    Remets dans l'ordre les étapes de validation du formulaire MissionPlanner de la leçon sur la gestion des événements :

Articles utiles