Kurs JavaScript i React · Moduł 3: Stan i zdarzenia

Formularze w React - kontrolowane komponenty

6 min czytania
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:

  1. Kontrolowane komponenty - React zarządza wartością inputów przez value + onChange
  2. Jeden state dla wielu pól - użyj obiektu stanu z dynamicznym [name]: value
  3. Checkboxy - używaj checked zamiast value, odczytuj event.target.checked
  4. Select - kontroluj przez value na <select>, nie na <option>
  5. preventDefault() - zawsze blokuj domyślne wysyłanie formularza
  6. Walidacja - sprawdzaj dane w handlerze onSubmit przed wysłaniem
  7. 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. 1. Czy można używać wielu useState w jednym komponencie?

  2. 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ń:

Przydatne artykuły