W kosmicznej podrozy przez galaktyki React, formularze to jeden z najwazniejszych modulow komunikacyjnych statku. Kazdy raport misji, kazde polecenie dla systemu nawigacyjnego i kazda wiadomosc do bazy - wszystko przechodzi przez formularze. React 19 wprowadza dwa rewolucyjne hooki:
useFormStatus i useActionState, ktore calkowicie zmieniaja sposob zarzadzania formularzami.Zanim poznamy nowe hooki, musimy zrozumiec koncepcje Server Actions. To funkcje oznaczone dyrektywa
'use server', ktore wykonuja sie po stronie serwera, ale moga byc wywolywane bezposrednio z formularzy w komponencie klienckim:1// actions.js
2'use server';
3
4async function submitMissionReport(formData) {
5 const missionName = formData.get('missionName');
6 const status = formData.get('status');
7
8 // To wykonuje sie na SERWERZE
9 await database.missions.create({
10 name: missionName,
11 status: status,
12 timestamp: new Date()
13 });
14
15 return { success: true, message: 'Raport zapisany!' };
16}Server Actions mozna przekazac bezposrednio do atrybutu
action formularza:1// MissionForm.jsx
2function MissionForm() {
3 return (
4 <form action={submitMissionReport}>
5 <input name="missionName" placeholder="Nazwa misji" />
6 <select name="status">
7 <option value="active">Aktywna</option>
8 <option value="completed">Ukonczona</option>
9 </select>
10 <button type="submit">Wyslij raport</button>
11 </form>
12 );
13}To podejscie eliminuje potrzebe recznego tworzenia endpointow API i zarzadzania stanami
loading/error - React obsluguje to automatycznie.useFormStatus to hook, ktory zwraca informacje o stanie wysylania formularza. Kluczowa zasada: musi byc uzywany w komponencie potomnym elementu <form>, nie w samym formularzu.1import { useFormStatus } from 'react-dom';
2
3// Komponent przycisku submit - WEWNATRZ formularza
4function SubmitButton() {
5 const { pending, data, method, action } = useFormStatus();
6
7 return (
8 <button
9 type="submit"
10 disabled={pending}
11 style={{
12 padding: '10px 20px',
13 background: pending ? '#333' : '#00d4ff',
14 color: pending ? '#666' : '#0a0e17',
15 border: 'none',
16 borderRadius: '6px',
17 cursor: pending ? 'wait' : 'pointer'
18 }}
19 >
20 {pending ? 'Wysylanie raportu...' : 'Wyslij raport misji'}
21 </button>
22 );
23}
24
25// pending - true gdy formularz jest w trakcie wysylania
26// data - obiekt FormData z danymi formularza
27// method - metoda HTTP (GET/POST)
28// action - referencja do funkcji action1import { useFormStatus } from 'react-dom';
2
3function MissionInput({ name, label, placeholder }) {
4 const { pending } = useFormStatus();
5 return (
6 <div style={{ marginBottom: '12px' }}>
7 <label style={{ color: '#8899aa', display: 'block', marginBottom: '4px' }}>
8 {label}
9 </label>
10 <input
11 name={name}
12 placeholder={placeholder}
13 disabled={pending}
14 style={{
15 width: '100%',
16 padding: '8px 12px',
17 background: pending ? '#1a1a2e' : '#0f1729',
18 color: '#e0e1dd',
19 border: '1px solid #2a3a5c',
20 borderRadius: '6px',
21 opacity: pending ? 0.5 : 1
22 }}
23 />
24 </div>
25 );
26}
27
28function LoadingOverlay() {
29 const { pending } = useFormStatus();
30 if (!pending) return null;
31 return (
32 <div style={{
33 position: 'absolute', top: 0, left: 0, right: 0, bottom: 0,
34 background: 'rgba(10, 14, 23, 0.7)',
35 display: 'flex', alignItems: 'center', justifyContent: 'center',
36 borderRadius: '8px'
37 }}>
38 <p style={{ color: '#00d4ff' }}>Transmisja w toku...</p>
39 </div>
40 );
41}
42
43function MissionReportForm({ submitAction }) {
44 return (
45 <form action={submitAction} style={{ position: 'relative' }}>
46 <LoadingOverlay />
47 <MissionInput name="mission" label="Nazwa misji" placeholder="Np. Alpha Centauri" />
48 <MissionInput name="coordinates" label="Wspolrzedne" placeholder="Np. 14h 29m 43s" />
49 <SubmitButton />
50 </form>
51 );
52}useActionState (wczesniej useFormState) to hook, ktory laczy Server Actions z zarzadzaniem stanem. Zwraca aktualny stan, opakowana akcje i flage isPending:1import { useActionState } from 'react';
2
3// Server Action zwracajaca nowy stan
4async function createMission(previousState, formData) {
5 const name = formData.get('name');
6 const destination = formData.get('destination');
7
8 if (!name || name.length < 3) {
9 return {
10 error: 'Nazwa misji musi miec co najmniej 3 znaki',
11 success: false
12 };
13 }
14
15 // Symulacja zapisu do bazy
16 await new Promise(resolve => setTimeout(resolve, 1000));
17
18 return {
19 success: true,
20 message: 'Misja utworzona pomyslnie!',
21 missionId: Math.random().toString(36).substr(2, 9)
22 };
23}
24
25function CreateMissionForm() {
26 const [state, formAction, isPending] = useActionState(
27 createMission,
28 { error: null, success: false } // Stan poczatkowy
29 );
30
31 return (
32 <form action={formAction}>
33 <h2 style={{ color: '#00d4ff' }}>Nowa misja kosmiczna</h2>
34
35 <input name="name" placeholder="Nazwa misji" />
36 <input name="destination" placeholder="Cel podrozy" />
37
38 <button type="submit" disabled={isPending}>
39 {isPending ? 'Tworzenie misji...' : 'Utworz misje'}
40 </button>
41
42 {state.error && (
43 <p style={{ color: '#ff6b6b' }}>{state.error}</p>
44 )}
45
46 {state.success && (
47 <p style={{ color: '#00ff88' }}>{state.message} (ID: {state.missionId})</p>
48 )}
49 </form>
50 );
51}Hook
useActionState przyjmuje trzy argumenty:(previousState, formData) => newStateZwraca tablice z trzema elementami:
<form action>Jedna z najwiekszych zalet nowych hookow formularzy to progressive enhancement - formularze dzialaja nawet bez JavaScript po stronie klienta:
1// Ten formularz dziala ZANIM JavaScript sie zaladuje!
2function SearchForm() {
3 const [state, searchAction, isPending] = useActionState(
4 async (prev, formData) => {
5 const query = formData.get('query');
6 const results = await searchGalaxy(query);
7 return { results, query };
8 },
9 { results: [], query: '' }
10 );
11
12 return (
13 <form action={searchAction}>
14 <input
15 name="query"
16 defaultValue={state.query}
17 placeholder="Szukaj gwiazd, planet, mgławic..."
18 />
19 <button type="submit" disabled={isPending}>
20 {isPending ? 'Skanowanie...' : 'Skanuj galaktykę'}
21 </button>
22
23 {state.results.length > 0 && (
24 <ul>
25 {state.results.map(r => (
26 <li key={r.id}>{r.name} - {r.type}</li>
27 ))}
28 </ul>
29 )}
30 </form>
31 );
32}Formularz wyslany przed zaladowaniem JavaScript zostanie obsluzony przez serwer, a po hydracji React przejmie kontrole - uzytkownik nie zauwazy roznicy.
useActionState swietnie nadaje sie do walidacji po stronie serwera z natychmiastowym feedbackiem:1async function validateAndSubmit(prevState, formData) {
2 const errors = {};
3
4 const name = formData.get('crewName');
5 const role = formData.get('role');
6 const experience = parseInt(formData.get('experience'));
7
8 if (!name || name.length < 2) errors.crewName = 'Imie musi miec min. 2 znaki';
9 if (!role) errors.role = 'Wybierz role';
10 if (isNaN(experience) || experience < 0) errors.experience = 'Nieprawidlowe doswiadczenie';
11
12 if (Object.keys(errors).length > 0) {
13 return { errors, success: false, values: { name, role, experience } };
14 }
15
16 await saveCrewMember({ name, role, experience });
17 return { errors: {}, success: true, message: 'Czlonek zalogi dodany!' };
18}
19
20function CrewRegistrationForm() {
21 const [state, formAction, isPending] = useActionState(
22 validateAndSubmit,
23 { errors: {}, success: false }
24 );
25
26 return (
27 <form action={formAction}>
28 <input name="crewName" defaultValue={state.values?.name || ''} />
29 {state.errors?.crewName && <span style={{color:'red'}}>{state.errors.crewName}</span>}
30
31 <select name="role" defaultValue={state.values?.role || ''}>
32 <option value="">Wybierz role</option>
33 <option value="pilot">Pilot</option>
34 <option value="engineer">Inzynier</option>
35 <option value="scientist">Naukowiec</option>
36 </select>
37 {state.errors?.role && <span style={{color:'red'}}>{state.errors.role}</span>}
38
39 <input name="experience" type="number" defaultValue={state.values?.experience || ''} />
40 {state.errors?.experience && <span style={{color:'red'}}>{state.errors.experience}</span>}
41
42 <button type="submit" disabled={isPending}>
43 {isPending ? 'Zapisywanie...' : 'Dodaj do zalogi'}
44 </button>
45
46 {state.success && <p style={{color:'green'}}>{state.message}</p>}
47 </form>
48 );
49}Nowe hooki formularzy w React 19 to fundamentalna zmiana w obsludze formularzy:
useFormStatus - dostarcza informacje o stanie wysylania formularza w komponentach potomnychuseActionState - laczy Server Actions z zarzadzaniem stanem, walidacja i progressive enhancementTe hooki eliminuja wiele boilerplate'u zwiazanego z obsluga formularzy - nie potrzebujesz juz recznego
onSubmit, useState dla kazdego pola, ani osobnego stanu isLoading. React 19 zarzadza tym automatycznie, jak autopilot na statku kosmicznym.