Kurs JavaScript i React · Moduł 3: Stan i zdarzenia
Formularze w React - kontrolowane komponenty
W tej lekcji6
Na pokładzie statku kosmicznego React formularze to jak panele konfiguracyjne - służą do wprowadzania danych nawigacyjnych, ustawiania parametrów misji i komunikacji z centrum dowodzenia. W tej lekcji poznasz kontrolowane komponenty - wzorzec, w którym React przejmuje pełną kontrolę nad danymi formularza, tak jak komputer pokładowy kontroluje każdy system statku.
Kontrolowane vs niekontrolowane komponenty
W React wyróżniamy dwa podejścia do obsługi formularzy:
Kontrolowane komponenty - React zarządza wartością inputa poprzez state. Każda zmiana przechodzi przez useState i 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}Niekontrolowane komponenty - przeglądarka zarządza wartością, a my odczytujemy ją przez 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">Wyślij</button>
13 </form>
14 );
15}Dlaczego kontrolowane komponenty są lepsze? Tak jak komputer pokładowy statku musi znać stan każdego systemu w każdej chwili, kontrolowane komponenty dają Reactowi pełny wgląd w dane formularza. Dzięki temu możesz walidować, formatować i reagować na zmiany w czasie rzeczywistym.
Obsługa pól tekstowych z useState
Prosty input tekstowy
1function PilotRegistration() {
2 const [pilotName, setPilotName] = useState("");
3 const [callSign, setCallSign] = useState("");
4
5 return (
6 <div className="registration-form">
7 <h2>Rejestracja pilota</h2>
8
9 <label>Imię pilota:</label>
10 <input
11 type="text"
12 value={pilotName}
13 onChange={(e) => setPilotName(e.target.value)}
14 placeholder="Wpisz imię..."
15 />
16
17 <label>Znak wywoławczy:</label>
18 <input
19 type="text"
20 value={callSign}
21 onChange={(e) => setCallSign(e.target.value.toUpperCase())}
22 placeholder="Np. ALPHA-7"
23 />
24
25 <p>Pilot: {pilotName} ({callSign})</p>
26 </div>
27 );
28}Zarządzanie wieloma polami jednym stanem
Zamiast tworzyć osobny useState dla każdego pola, możesz użyć jednego obiektu stanu - jak centralny rejestr danych misji:
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>Konfiguracja misji</h2>
20
21 <label>Nazwa misji:</label>
22 <input
23 type="text"
24 name="missionName"
25 value={formData.missionName}
26 onChange={handleChange}
27 />
28
29 <label>Cel podróży:</label>
30 <input
31 type="text"
32 name="destination"
33 value={formData.destination}
34 onChange={handleChange}
35 />
36
37 <label>Rozmiar załogi:</label>
38 <input
39 type="number"
40 name="crewSize"
41 value={formData.crewSize}
42 onChange={handleChange}
43 />
44
45 <label>Priorytet:</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}Kluczowa sztuczka: atrybut name na inputach odpowiada kluczom w obiekcie stanu, a [name]: value (computed property name) dynamicznie aktualizuje odpowiednie pole.
Checkboxy i elementy select
Checkbox - przełączniki systemów
Checkboxy w React używają checked zamiast 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>Systemy statku</h2>
20
21 <label>
22 <input
23 type="checkbox"
24 name="engines"
25 checked={systems.engines}
26 onChange={handleToggle}
27 />
28 Silniki
29 </label>
30
31 <label>
32 <input
33 type="checkbox"
34 name="shields"
35 checked={systems.shields}
36 onChange={handleToggle}
37 />
38 Tarcze ochronne
39 </label>
40
41 <label>
42 <input
43 type="checkbox"
44 name="weapons"
45 checked={systems.weapons}
46 onChange={handleToggle}
47 />
48 Systemy uzbrojenia
49 </label>
50
51 <label>
52 <input
53 type="checkbox"
54 name="lifeSupport"
55 checked={systems.lifeSupport}
56 onChange={handleToggle}
57 />
58 Podtrzymywanie życia
59 </label>
60
61 <div className="status">
62 Aktywne systemy: {Object.entries(systems)
63 .filter(([, active]) => active)
64 .map(([name]) => name)
65 .join(", ")}
66 </div>
67 </div>
68 );
69}Select - wybór z listy
Element <select> w React działa jak inne kontrolowane inputy - wartość kontrolujemy przez value i 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: "Jowisz" },
8 { value: "saturn", label: "Saturn" },
9 { value: "alpha-centauri", label: "Alfa Centauri" },
10 { value: "andromeda", label: "Galaktyka Andromedy" }
11 ];
12
13 return (
14 <div className="nav-panel">
15 <h2>Panel nawigacji</h2>
16
17 <label>Cel podróży:</label>
18 <select
19 value={destination}
20 onChange={(e) => setDestination(e.target.value)}
21 >
22 <option value="">-- Wybierz cel --</option>
23 {destinations.map((dest) => (
24 <option key={dest.value} value={dest.value}>
25 {dest.label}
26 </option>
27 ))}
28 </select>
29
30 <label>Prędkość 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 (maksimum)</option>
39 </select>
40
41 {destination && (
42 <p>Kurs ustawiony: {destination} z prędkością Warp {warpSpeed}</p>
43 )}
44 </div>
45 );
46}Obsługa wysyłania formularza
Wysyłanie formularza to jak nadanie rozkazu z centrum dowodzenia - musi być kontrolowane i bezpieczne:
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 // Walidacja
23 if (!formData.missionName.trim() || !formData.commander.trim()) {
24 alert("Wypełnij wszystkie wymagane pola!");
25 return;
26 }
27
28 console.log("Raport misji:", 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>Raport wysłany!</h2>
47 <p>Misja: {formData.missionName}</p>
48 <button onClick={handleReset}>Nowy raport</button>
49 </div>
50 );
51 }
52
53 return (
54 <form onSubmit={handleSubmit} className="mission-report">
55 <h2>Raport misji</h2>
56
57 <label>Nazwa misji *:</label>
58 <input
59 type="text"
60 name="missionName"
61 value={formData.missionName}
62 onChange={handleChange}
63 required
64 />
65
66 <label>Dowódca *:</label>
67 <input
68 type="text"
69 name="commander"
70 value={formData.commander}
71 onChange={handleChange}
72 required
73 />
74
75 <label>Status:</label>
76 <select name="status" value={formData.status} onChange={handleChange}>
77 <option value="in-progress">W trakcie</option>
78 <option value="completed">Zakończona</option>
79 <option value="failed">Niepowodzenie</option>
80 </select>
81
82 <label>Opis:</label>
83 <textarea
84 name="description"
85 value={formData.description}
86 onChange={handleChange}
87 rows={4}
88 placeholder="Opisz przebieg misji..."
89 />
90
91 <label>
92 <input
93 type="checkbox"
94 name="isClassified"
95 checked={formData.isClassified}
96 onChange={handleChange}
97 />
98 Raport tajny
99 </label>
100
101 <div className="form-actions">
102 <button type="submit">Wyślij raport</button>
103 <button type="button" onClick={handleReset}>Wyczyść</button>
104 </div>
105 </form>
106 );
107}Lifting state up - podnoszenie stanu
Kiedy kilka komponentów musi współdzielić dane formularza, przenosimy stan do ich wspólnego rodzica - jak centrala dowodzenia koordynująca dane z wielu paneli:
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 // Rodzic pamięta tylko to, co wpisano ostatnio, i w której skali
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>Konwerter temperatury kadłuba</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}W tym wzorcu:
- Stan (
temperature: ostatnio wpisana wartość i jej skala) żyje w rodzicu (TemperatureConverter) - Komponenty dzieci (
TemperatureInput) otrzymują wartość przez props - Zmiany przekazywane są w górę przez callback
onChange - Rodzic przelicza i synchronizuje obie wartości
Rodzic nie zapisuje gotowych stopni Celsjusza, tylko ostatnio wpisaną wartość i jej skalę. Dzięki temu pole, w którym piszesz, pokazuje dokładnie to, co wpisujesz (także samo - albo 10), a drugie pole jest przeliczane i zaokrąglane do dwóch miejsc po przecinku.
Podsumowanie
Formularze w React - kluczowe zasady:
- Kontrolowane komponenty - React zarządza wartością inputów przez
value+onChange - Jeden state dla wielu pól - użyj obiektu stanu z dynamicznym
[name]: value - Checkboxy - używaj
checkedzamiastvalue, odczytujevent.target.checked - Select - kontroluj przez
valuena<select>, nie na<option> - preventDefault() - zawsze blokuj domyślne wysyłanie formularza
- Walidacja - sprawdzaj dane w handlerze
onSubmitprzed wysłaniem - Lifting state up - przenieś stan do wspólnego rodzica, gdy komponenty muszą współdzielić dane
Kod do tej lekcji: 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: 'Łazik' },
21 { id: 'drone', label: 'Dron' },
22 { id: 'satellite', label: 'Satelita' },
23 { id: 'telescope', label: 'Teleskop' },
24 { id: 'lab', label: 'Laboratorium' }
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 = 'Nazwa misji jest wymagana';
50 if (!formData.duration || formData.duration < 1) newErrors.duration = 'Czas trwania musi być większy niż 0';
51 if (!formData.launchDate) newErrors.launchDate = 'Data startu jest wymagana';
52 if (formData.equipment.length === 0) newErrors.equipment = 'Wybierz przynajmniej jedno wyposażenie';
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' }}>Misja zaplanowana!</h1>
81 <div style={{ backgroundColor: '#f5f5f5', padding: '20px', borderRadius: '12px', maxWidth: '500px', margin: '20px auto', textAlign: 'left' }}>
82 <p><strong>Nazwa:</strong> {formData.missionName}</p>
83 <p><strong>Cel:</strong> {formData.destination}</p>
84 <p><strong>Załoga:</strong> {formData.crewSize} osób</p>
85 <p><strong>Czas trwania:</strong> {formData.duration} dni</p>
86 <p><strong>Data startu:</strong> {formData.launchDate}</p>
87 <p><strong>Wyposażenie:</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 Zaplanuj nową misję
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' }}>Planowanie Misji Kosmicznej</h1>
99
100 <form onSubmit={handleSubmit} style={{ display: 'flex', flexDirection: 'column', gap: '20px' }}>
101 <div>
102 <label style={{ display: 'block', marginBottom: '5px', fontWeight: 'bold' }}>Nazwa misji *</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' }}>Cel podróży</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">Księżyc</option>
115 <option value="venus">Wenus</option>
116 <option value="jupiter">Jowisz</option>
117 <option value="saturn">Saturn</option>
118 </select>
119 </div>
120
121 <div>
122 <label style={{ display: 'block', marginBottom: '5px', fontWeight: 'bold' }}>Wielkość załogi: {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' }}>Czas trwania (dni) *</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' }}>Data startu *</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' }}>Priorytet</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' ? 'Niski' : p === 'medium' ? 'Średni' : 'Wysoki'}
151 </label>
152 ))}
153 </div>
154 </div>
155
156 <div>
157 <label style={{ display: 'block', marginBottom: '10px', fontWeight: 'bold' }}>Wyposażenie *</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' }}>Misja pilna</span>
179 </label>
180 </div>
181
182 <div>
183 <label style={{ display: 'block', marginBottom: '5px', fontWeight: 'bold' }}>Notatki</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 Zaplanuj misję
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 Wyczyść
195 </button>
196 </div>
197 </form>
198 </div>
199 );
200}
201
202export default function App() {
203 return <SpaceMissionForm />;
204}Pamiętaj: Kontrolowane komponenty to jak system sterowania statkiem - komputer pokładowy (React) wie o każdej zmianie w czasie rzeczywistym i może natychmiast zareagować!
Widzisz błąd w tej lekcji?
Sprawdź się
Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.
1. Czy można używać wielu useState w jednym komponencie?
2. Jaka jest różnica między state a props?
To 2 z 9 pytań do tej lekcji. Pozostałe rozwiążesz w grze.
Zadania praktyczne w grze
- Klikanie w kolejności
Zbuduj handler onSubmit zapobiegający przeładowaniu strony:
- Układanie w pionie
Ułóż etapy tworzenia kontrolowanego formularza w React:
- Edytor kodu
Stwórz komponent EmergencyAlert ze stanem message wyświetlanym w akapicie <p> i wyrenderuj go w App. Dodaj przycisk ALARM: onClick ustawia komunikat 'Alarm aktywowany!', a onMouseEnter komunikat 'Uwaga: kursor nad przyciskiem alarmu'. Dodaj też pole <input> z onKeyDown, które po wciśnięciu Enter (e.key === 'Enter') ustawia komunikat 'Alarm uruchomiony z klawiatury', a inne klawisze ignoruje.
- Edytor kodu
Stwórz rodzica ColonyControl ze stanem powerLevel (useState(50)) i dwoje dzieci bez własnego stanu: PowerPlant({ powerLevel, onPowerChange }) wyświetla poziom mocy i ma przyciski +10 i -10, które wywołują onPowerChange z nową wartością, oraz LifeSupport({ powerLevel }), który wyświetla 'Systemy podtrzymywania życia: aktywne', gdy powerLevel >= 30, a w przeciwnym razie 'ALARM: za mało mocy'. ColonyControl przekazuje obu dzieciom ten sam powerLevel. Wyrenderuj ColonyControl w App.
- Edytor kodu
Stwórz komponent ColonyDashboard z czterema osobnymi stanami useState: temperature (np. -20), pressure (np. 600), oxygen (np. 21) i power (np. 80), i wyrenderuj go w App. Wyświetl wszystkie cztery wartości, a dla każdej dodaj przycisk, który zmienia tylko ją (np. Ogrzej +5, Ciśnienie +10, Tlen +1, Zasilanie -10).
- Klikanie w kolejności
Zbuduj wyrażenie pobierające wartość z pola input w event handlerze:
- Układanie w poziomie
Ułóż składnię importu hooka useState z React:
- Klikanie w kolejności
Ułóż etapy obsługi zdarzenia:
- Układanie w pionie
Ułóż sposoby przechowywania stanu od najprostszego do najbardziej rozbudowanego, tak jak w lekcji o przepływie danych:
- Układanie w pionie
Ułóż, co się dzieje po wpisaniu jednego znaku w kontrolowane pole <input value={name} onChange={(e) => setName(e.target.value)} />:
- Klikanie w kolejności
Ułóż kroki aktualizacji stanu:
- Układanie w poziomie
Ułóż poprawny atrybut onChange dla kontrolowanego inputa:
- Klikanie w kolejności
Kliknij elementy w kolejności tworzenia kontrolowanego inputa z onChange:
- Układanie w pionie
Ułóż kroki cyklu aktualizacji stanu w React od zdarzenia do aktualizacji DOM:
- Układanie w poziomie
Ułóż poprawną aktualizację jednej właściwości obiektu w useState:
- Układanie w pionie
Ułóż kroki walidacji w formularzu MissionPlanner z lekcji o obsłudze zdarzeń: