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.
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.
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}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 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}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}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}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:
celsius) żyje w rodzicu (TemperatureConverter)TemperatureInput) otrzymują wartość przez propsonChangeFormularze w React - kluczowe zasady:
value + onChange[name]: valuechecked zamiast value, odczytuj event.target.checkedvalue na <select>, nie na <option>onSubmit przed wysłaniemPamię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ć!