Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie
CodeWorlds

Formularze w React - kontrolowane komponenty

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 = () => {
5    console.log(inputRef.current.value);
6  };
7
8  return <input type="text" ref={inputRef} />;
9}

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>Call sign:</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}

Kluczowy trick: 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  const [celsius, setCelsius] = useState("");
16
17  const handleCelsiusChange = (value) => {
18    setCelsius(value);
19  };
20
21  const handleFahrenheitChange = (value) => {
22    const c = ((parseFloat(value) - 32) * 5) / 9;
23    setCelsius(isNaN(c) ? "" : c.toFixed(2));
24  };
25
26  const fahrenheit = celsius !== ""
27    ? ((parseFloat(celsius) * 9) / 5 + 32).toFixed(2)
28    : "";
29
30  return (
31    <div className="converter">
32      <h2>Konwerter temperatury kadłuba</h2>
33      <TemperatureInput
34        label="Celsius"
35        value={celsius}
36        onChange={handleCelsiusChange}
37      />
38      <TemperatureInput
39        label="Fahrenheit"
40        value={fahrenheit}
41        onChange={handleFahrenheitChange}
42      />
43    </div>
44  );
45}

W tym wzorcu:

  • Stan (
    celsius
    ) ż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

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

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ć!

Przejdź do CodeWorlds