Utilizziamo i cookie per migliorare la tua esperienza sul sito
CodeWorlds

useFormStatus i useActionState - hooki formularzy React 19

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.

Server Actions - nowy paradygmat

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 - status formularza w komponentach potomnych

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 action

Praktyczny przyklad z useFormStatus

1import { 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 - pelne zarzadzanie stanem akcji

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}

Parametry useActionState

Hook

useActionState
przyjmuje trzy argumenty:

  1. action - funkcja asynchroniczna
    (previousState, formData) => newState
  2. initialState - poczatkowy stan (dowolny obiekt)
  3. permalink (opcjonalny) - URL dla progressive enhancement

Zwraca tablice z trzema elementami:

  1. state - aktualny stan (wynik ostatniej akcji lub initialState)
  2. formAction - opakowana akcja do przekazania do
    <form action>
  3. isPending - boolean, czy akcja jest w trakcie wykonywania

Progressive Enhancement

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.

Walidacja formularzy z useActionState

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}

Podsumowanie

Nowe hooki formularzy w React 19 to fundamentalna zmiana w obsludze formularzy:

  1. useFormStatus
    - dostarcza informacje o stanie wysylania formularza w komponentach potomnych
  2. useActionState
    - laczy Server Actions z zarzadzaniem stanem, walidacja i progressive enhancement
  3. Server Actions - funkcje serwerowe wywolywane bezposrednio z formularzy
  4. Progressive Enhancement - formularze dzialaja przed zaladowaniem JavaScript

Te 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.

Vai a CodeWorlds