Kurs JavaScript i React · Moduł 9: Nowoczesne hooki React
useFormStatus i useActionState - hooki formularzy React 19
W tej lekcji6
W kosmicznej podróży przez galaktyki React formularze to jeden z najważniejszych modułów komunikacyjnych statku. Każdy raport misji, każde polecenie dla systemu nawigacyjnego i każda wiadomość do bazy przechodzi przez formularz. Do tej pory oznaczało to ręczne onSubmit, e.preventDefault(), osobny stan ładowania i kolejny na błędy. React 19 wprowadza akcje formularzy i dwa hooki, useFormStatus i useActionState, które przejmują większość tej pracy.
Server Actions - nowy paradygmat
Zanim poznamy nowe hooki, musimy zrozumieć koncepcję Server Actions. To funkcje oznaczone dyrektywą 'use server', które wykonują się na serwerze, ale można je wywołać prosto z formularza w komponencie klienckim. Dyrektywa działa tylko we frameworku z React Server Components, np. w Next.js z App Routerem, bo ktoś musi uruchomić tę funkcję po stronie serwera. Plik z akcjami wygląda tak:
1// actions.js
2'use server';
3
4export async function submitMissionReport(formData) {
5 const missionName = formData.get('missionName');
6 const status = formData.get('status');
7
8 // To wykonuje się 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}Funkcja jest eksportowana, bo komponent importuje ją jak zwykły moduł. Framework nie wysyła jej kodu do przeglądarki, tylko referencję, przez którą formularz wywoła serwer. Server Action przekazujesz bezpośrednio do atrybutu action formularza:
1// MissionForm.jsx
2import { submitMissionReport } from './actions';
3
4function MissionForm() {
5 return (
6 <form action={submitMissionReport}>
7 <input name="missionName" placeholder="Nazwa misji" />
8 <select name="status">
9 <option value="active">Aktywna</option>
10 <option value="completed">Ukończona</option>
11 </select>
12 <button type="submit">Wyślij raport</button>
13 </form>
14 );
15}Nie piszesz endpointu API ani obsługi onSubmit: React zbiera pola formularza w obiekt FormData i przekazuje go akcji. Ważne: akcją formularza może być też zwykła funkcja asynchroniczna zdefiniowana w komponencie klienckim, bez 'use server'. Tak działają kolejne przykłady i edytor w tej lekcji, bo useFormStatus i useActionState pracują z obydwoma rodzajami akcji.
useFormStatus - status formularza w komponentach potomnych
useFormStatus to hook z pakietu react-dom, który zwraca informacje o stanie wysyłania formularza. Kluczowa zasada: musi być używany w komponencie potomnym elementu <form>, a nie w komponencie, który ten formularz renderuje.
1import { useFormStatus } from 'react-dom';
2
3// Komponent przycisku submit - WEWNĄTRZ 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 ? 'Wysyłanie raportu...' : 'Wyślij raport misji'}
21 </button>
22 );
23}
24
25// pending - true, gdy formularz jest w trakcie wysyłania
26// data - obiekt FormData z danymi wysyłanego formularza
27// method - metoda formularza: 'get' albo 'post'
28// action - funkcja przekazana do atrybutu actionPrzycisk sam wie, że formularz się wysyła, więc rodzic nie musi przekazywać mu stanu przez propsy. Gdy nic się nie wysyła, pending ma wartość false, a pozostałe pola null.
Praktyczny przykład z useFormStatus
Hook przydaje się nie tylko w przycisku. Każdy komponent wewnątrz formularza może zapytać o jego stan, więc pola same się blokują, a nakładka pokazuje komunikat o transmisji. Formularz MissionReportForm nie przekazuje im żadnych propsów ze stanem:
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="Współrzędne" placeholder="Np. 14h 29m 43s" />
49 <SubmitButton />
50 </form>
51 );
52}SubmitButton z poprzedniego przykładu działa tu bez zmian. Gdyby wywołać useFormStatus w samym MissionReportForm, pending zawsze miałoby wartość false, bo hook czyta formularz nadrzędny, a nie ten, który komponent sam renderuje.
useActionState - pełne zarządzanie stanem akcji
useActionState (wcześniej useFormState z pakietu react-dom) to hook z pakietu react, który łączy akcję, serwerową albo zwykłą, z zarządzaniem stanem. Zwraca aktualny stan, opakowaną akcję i flagę isPending:
1import { useActionState } from 'react';
2
3// Akcja zwracająca nowy stan: tu zwykła funkcja asynchroniczna w przeglądarce
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 mieć 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 pomyślnie!',
21 missionId: Math.random().toString(36).slice(2, 11)
22 };
23}
24
25function CreateMissionForm() {
26 const [state, formAction, isPending] = useActionState(
27 createMission,
28 { error: null, success: false } // Stan początkowy
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 podróży" />
37
38 <button type="submit" disabled={isPending}>
39 {isPending ? 'Tworzenie misji...' : 'Utwórz misję'}
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}Akcja dostaje poprzedni stan jako pierwszy argument, a dane formularza jako drugi. To, co zwróci, staje się nowym state, a isPending ma wartość true, dopóki akcja się nie skończy. Kolejne wysyłki React ustawia w kolejce, więc każda akcja dostaje stan zwrócony przez poprzednią.
Parametry useActionState
Hook useActionState przyjmuje trzy argumenty:
- action - funkcja
(previousState, formData) => newState, zwykle asynchroniczna - initialState - początkowy stan (dowolna wartość, najczęściej obiekt)
- permalink (opcjonalny) - adres strony z formularzem, potrzebny przy progressive enhancement
Zwraca tablicę z trzema elementami:
- state - aktualny stan (wynik ostatniej akcji albo initialState)
- formAction - opakowana akcja do przekazania do
<form action> - isPending - wartość logiczna: czy akcja jest w trakcie wykonywania
Progressive Enhancement
Jedna z największych zalet akcji formularzy to progressive enhancement: formularz może zadziałać, zanim JavaScript się załaduje. Warunek jest jeden: akcja musi być funkcją serwerową ('use server'), a strona renderowana na serwerze przez framework. Wtedy przeglądarka wyśle formularz jak zwykły formularz HTML, a serwer wykona akcję. Najpierw akcja:
1// search-actions.js
2'use server';
3
4export async function searchGalaxy(previousState, formData) {
5 const query = formData.get('query');
6 const results = await findStars(query);
7 return { results, query };
8}Komponent kliencki podaje ją do useActionState, a trzeci argument to permalink, czyli adres strony z formularzem:
1// SearchForm.jsx
2'use client';
3import { useActionState } from 'react';
4import { searchGalaxy } from './search-actions';
5
6function SearchForm() {
7 const [state, searchAction, isPending] = useActionState(
8 searchGalaxy,
9 { results: [], query: '' },
10 '/szukaj'
11 );
12
13 return (
14 <form action={searchAction}>
15 <input
16 name="query"
17 defaultValue={state.query}
18 placeholder="Szukaj gwiazd, planet, mgławic..."
19 />
20 <button type="submit" disabled={isPending}>
21 {isPending ? 'Skanowanie...' : 'Skanuj galaktykę'}
22 </button>
23
24 {state.results.length > 0 && (
25 <ul>
26 {state.results.map(r => (
27 <li key={r.id}>{r.name} - {r.type}</li>
28 ))}
29 </ul>
30 )}
31 </form>
32 );
33}Jeśli użytkownik wyśle formularz przed hydratacją, przeglądarka przejdzie pod adres /szukaj, a serwer odeśle stronę z wynikiem. Po załadowaniu JavaScriptu React przejmuje formularz i wysyła go już bez przeładowania strony. Z akcją zapisaną jako zwykła funkcja w komponencie klienckim, jak w poprzednich przykładach, formularz zadziała dopiero po załadowaniu JavaScriptu.
Walidacja formularzy z useActionState
useActionState świetnie nadaje się do walidacji po stronie serwera z natychmiastowym komunikatem zwrotnym:
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 = 'Imię musi mieć min. 2 znaki';
9 if (!role) errors.role = 'Wybierz rolę';
10 if (isNaN(experience) || experience < 0) errors.experience = 'Nieprawidłowe doświadczenie';
11
12 if (Object.keys(errors).length > 0) {
13 return { errors, success: false, values: { name, role, experience } };
14 }
15
16 // saveCrewMember to Twoja funkcja zapisu, np. zapytanie do bazy
17 await saveCrewMember({ name, role, experience });
18 return { errors: {}, success: true, message: 'Członek załogi dodany!' };
19}
20
21function CrewRegistrationForm() {
22 const [state, formAction, isPending] = useActionState(
23 validateAndSubmit,
24 { errors: {}, success: false }
25 );
26
27 return (
28 <form action={formAction}>
29 <input name="crewName" defaultValue={state.values?.name || ''} />
30 {state.errors?.crewName && <span style={{color:'red'}}>{state.errors.crewName}</span>}
31
32 <select name="role" defaultValue={state.values?.role || ''}>
33 <option value="">Wybierz rolę</option>
34 <option value="pilot">Pilot</option>
35 <option value="engineer">Inżynier</option>
36 <option value="scientist">Naukowiec</option>
37 </select>
38 {state.errors?.role && <span style={{color:'red'}}>{state.errors.role}</span>}
39
40 <input name="experience" type="number" defaultValue={state.values?.experience || ''} />
41 {state.errors?.experience && <span style={{color:'red'}}>{state.errors.experience}</span>}
42
43 <button type="submit" disabled={isPending}>
44 {isPending ? 'Zapisywanie...' : 'Dodaj do załogi'}
45 </button>
46
47 {state.success && <p style={{color:'green'}}>{state.message}</p>}
48 </form>
49 );
50}Zwróć uwagę na values w odpowiedzi z błędami. Po zakończeniu akcji React resetuje formularz z niekontrolowanymi polami, a wtedy każde pole wraca do swojego defaultValue. Ponieważ defaultValue czyta wartości ze stanu, użytkownik po błędzie widzi dalej to, co wpisał, a po udanym zapisie dostaje czysty formularz.
Podsumowanie
Nowe hooki formularzy w React 19 to fundamentalna zmiana w obsłudze formularzy:
useFormStatus- dostarcza informacje o stanie wysyłania formularza w komponentach potomnychuseActionState- łączy akcję z zarządzaniem stanem, walidacją i progressive enhancement- Server Actions - funkcje serwerowe wywoływane bezpośrednio z formularzy (we frameworku z React Server Components)
- Progressive Enhancement - formularz z akcją serwerową działa także przed załadowaniem JavaScript
Te hooki eliminują wiele boilerplate'u związanego z obsługą formularzy: nie potrzebujesz już ręcznego onSubmit, useState dla każdego pola ani osobnego stanu ładowania. Pamiętaj: akcja formularza to rozkaz dla centrum kontroli, a useActionState i useFormStatus meldują, na jakim etapie jest jego wykonanie.
Kod do tej lekcji: App.jsx
1import { useActionState } from 'react';
2import { useFormStatus } from 'react-dom';
3
4// Akcja formularza. W Next.js mogłaby być funkcją serwerową z dyrektywą 'use server',
5// tu jest zwykłą funkcją asynchroniczną, więc działa w przeglądarce.
6async function submitMissionReport(previousState, formData) {
7 // Symulacja opóźnienia łącza z bazą
8 await new Promise(resolve => setTimeout(resolve, 1500));
9 const name = formData.get('missionName');
10 const dest = formData.get('destination');
11 const priority = formData.get('priority');
12 const values = { missionName: name, destination: dest, priority };
13
14 // Przy błędzie zwracamy wpisane wartości: React po akcji resetuje formularz
15 if (!name || name.length < 3) {
16 return { ...previousState, error: 'Nazwa misji musi mieć co najmniej 3 znaki', success: false, values };
17 }
18 if (!dest) {
19 return { ...previousState, error: 'Podaj cel misji', success: false, values };
20 }
21
22 const entry = {
23 missionId: 'MSN-' + String(previousState.history.length + 1).padStart(3, '0'),
24 message: `Misja "${name}" do ${dest} (priorytet: ${priority}) utworzona!`
25 };
26 // Historia rośnie z akcji na akcję, bo akcja dostaje poprzedni stan
27 return { error: null, success: true, values: {}, ...entry, history: [entry, ...previousState.history] };
28}
29
30// Przycisk czyta stan formularza przez useFormStatus, bez żadnych propsów
31function SubmitButton() {
32 const { pending } = useFormStatus();
33 return (
34 <button
35 type="submit"
36 disabled={pending}
37 style={{
38 width: '100%', padding: '12px', marginTop: '12px',
39 background: pending ? '#1a2a3c' : 'linear-gradient(135deg, #00d4ff, #0088cc)',
40 color: pending ? '#556677' : '#0a0e17',
41 border: 'none', borderRadius: '8px', cursor: pending ? 'wait' : 'pointer',
42 fontSize: '14px', fontWeight: 'bold', transition: 'all 0.3s',
43 }}
44 >
45 {pending ? 'Transmisja w toku...' : 'Wyślij raport misji'}
46 </button>
47 );
48}
49
50// Pole samo blokuje się w czasie wysyłki formularza
51function MissionInput({ name, label, placeholder, type = 'text', defaultValue }) {
52 const { pending } = useFormStatus();
53 const fieldStyle = {
54 width: '100%', padding: '10px 12px', background: '#0f1729',
55 color: '#e0e1dd', border: '1px solid #2a3a5c', borderRadius: '6px',
56 opacity: pending ? 0.5 : 1, boxSizing: 'border-box',
57 };
58 return (
59 <div style={{ marginBottom: '12px' }}>
60 <label htmlFor={name} style={{ color: '#8892b0', fontSize: '12px', display: 'block', marginBottom: '4px' }}>
61 {label}
62 </label>
63 {type === 'select' ? (
64 <select id={name} name={name} defaultValue={defaultValue || 'medium'} disabled={pending} style={fieldStyle}>
65 <option value="high">Wysoki</option>
66 <option value="medium">Średni</option>
67 <option value="low">Niski</option>
68 </select>
69 ) : (
70 <input id={name} name={name} placeholder={placeholder} defaultValue={defaultValue || ''} disabled={pending} style={fieldStyle} />
71 )}
72 </div>
73 );
74}
75
76// Nakładka widoczna tylko w czasie wysyłki
77function LoadingOverlay() {
78 const { pending } = useFormStatus();
79 if (!pending) return null;
80 return (
81 <div style={styles.overlay}>
82 <div style={styles.spinner} />
83 <p style={{ color: '#00d4ff', marginTop: '10px', fontSize: '13px' }}>Przesyłanie do bazy...</p>
84 </div>
85 );
86}
87
88function MissionHistory({ missions }) {
89 if (missions.length === 0) return null;
90 return (
91 <div style={styles.history}>
92 <h3 style={{ color: '#00d4ff', fontSize: '14px', margin: '0 0 10px' }}>Historia misji</h3>
93 {missions.map((m) => (
94 <div key={m.missionId} style={styles.historyItem}>
95 <span style={styles.missionId}>{m.missionId}</span>
96 <span>{m.message}</span>
97 </div>
98 ))}
99 </div>
100 );
101}
102
103function App() {
104 const [state, formAction] = useActionState(submitMissionReport, {
105 error: null, success: false, values: {}, history: []
106 });
107
108 return (
109 <div style={styles.container}>
110 <header style={styles.header}>
111 <h1 style={styles.title}>Centrum Dowodzenia Misji</h1>
112 <p style={styles.subtitle}>useFormStatus i useActionState - hooki formularzy React 19</p>
113 </header>
114
115 <div style={styles.grid}>
116 <div style={styles.formPanel}>
117 <h2 style={{ color: '#00d4ff', fontSize: '16px', margin: '0 0 16px' }}>
118 Nowa misja kosmiczna
119 </h2>
120
121 <form action={formAction}>
122 <LoadingOverlay />
123 <MissionInput name="missionName" label="Nazwa misji" placeholder="Np. Eksploracja Proxima b" defaultValue={state.values.missionName} />
124 <MissionInput name="destination" label="Cel podróży" placeholder="Np. Alpha Centauri" defaultValue={state.values.destination} />
125 <MissionInput name="priority" label="Priorytet" type="select" defaultValue={state.values.priority} />
126 <SubmitButton />
127 </form>
128
129 {state.error && (
130 <div role="alert" style={styles.error}>
131 <span style={{ marginRight: '8px' }}>!</span> {state.error}
132 </div>
133 )}
134 {state.success && (
135 <div style={styles.success}>
136 <span style={{ marginRight: '8px' }}>OK</span> {state.message}
137 <div style={{ fontSize: '11px', color: '#00aa66', marginTop: '4px' }}>
138 ID: {state.missionId}
139 </div>
140 </div>
141 )}
142 </div>
143
144 <MissionHistory missions={state.history} />
145 </div>
146
147 <div style={styles.info}>
148 <h3 style={{ color: '#ffd700', fontSize: '13px', margin: '0 0 8px' }}>Jak to działa:</h3>
149 <ul style={{ margin: 0, paddingLeft: '20px', fontSize: '12px', color: '#8892b0' }}>
150 <li>useActionState łączy akcję ze stanem i zwraca [state, formAction, isPending]</li>
151 <li>useFormStatus w przycisku, polach i nakładce czyta stan wysyłki formularza</li>
152 <li>Akcja dostaje poprzedni stan i FormData z polami formularza</li>
153 <li>Po akcji React resetuje pola, dlatego przy błędzie wracają przez defaultValue</li>
154 </ul>
155 </div>
156 </div>
157 );
158}
159
160const styles = {
161 container: { fontFamily: 'system-ui', background: '#0a0e17', color: '#e0e1dd', minHeight: '100vh', padding: '20px' },
162 header: { textAlign: 'center', marginBottom: '20px' },
163 title: { fontSize: '22px', color: '#00d4ff', margin: '0 0 4px' },
164 subtitle: { fontSize: '12px', color: '#8892b0', margin: 0 },
165 grid: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '16px', maxWidth: '800px', margin: '0 auto' },
166 formPanel: { background: 'rgba(0,212,255,0.05)', borderRadius: '10px', padding: '20px', border: '1px solid rgba(0,212,255,0.15)', position: 'relative', overflow: 'hidden' },
167 overlay: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, background: 'rgba(10,14,23,0.85)', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', zIndex: 10, borderRadius: '10px' },
168 spinner: { width: '30px', height: '30px', border: '3px solid #1a3a5c', borderTopColor: '#00d4ff', borderRadius: '50%', animation: 'spin 1s linear infinite' },
169 error: { marginTop: '12px', padding: '10px 14px', background: 'rgba(255,107,107,0.1)', border: '1px solid rgba(255,107,107,0.3)', borderRadius: '6px', color: '#ff6b6b', fontSize: '13px' },
170 success: { marginTop: '12px', padding: '10px 14px', background: 'rgba(0,255,136,0.08)', border: '1px solid rgba(0,255,136,0.3)', borderRadius: '6px', color: '#00ff88', fontSize: '13px' },
171 history: { background: 'rgba(0,212,255,0.05)', borderRadius: '10px', padding: '16px', border: '1px solid rgba(0,212,255,0.15)' },
172 historyItem: { display: 'flex', gap: '8px', padding: '8px 10px', background: 'rgba(0,0,0,0.3)', borderRadius: '6px', marginBottom: '6px', fontSize: '12px', alignItems: 'center' },
173 missionId: { padding: '2px 8px', background: 'rgba(0,212,255,0.2)', borderRadius: '4px', color: '#00d4ff', fontSize: '10px', fontWeight: 'bold', whiteSpace: 'nowrap' },
174 info: { maxWidth: '800px', margin: '20px auto 0', padding: '14px', background: 'rgba(255,215,0,0.05)', border: '1px solid rgba(255,215,0,0.15)', borderRadius: '8px' },
175};
176
177export default App;Widzisz błąd w tej lekcji?
Sprawdź się
Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.
1. Gdzie musi być używany hook useFormStatus, aby prawidłowo odczytywać stan formularza?
2. Jakie trzy wartości zwraca hook useActionState?
To 2 z 3 pytań do tej lekcji. Pozostałe rozwiążesz w grze.
Zadania praktyczne w grze
- Edytor kodu
W pliku App.jsx jest formularz raportu misji i asynchroniczna akcja sendReport, która sprawdza nazwę misji i po pół sekundy odpowiada jak baza na Ziemi. Uzupełnij luki: ___BLANK1___ to metoda FormData, która odczytuje wartość pola po jego nazwie, ___BLANK2___ to hook Reacta 19, który łączy akcję ze stanem formularza i zwraca [state, formAction, isPending], ___BLANK3___ to atrybut elementu <form>, do którego przekazujesz formAction, a ___BLANK4___ to wartość, która blokuje przycisk na czas wysyłania. Nazwa krótsza niż 3 znaki ma pokazać komunikat błędu, a poprawna potwierdzenie z nazwą misji. Wyślij formularz w podglądzie i sprawdź oba przypadki.
- Klikanie w kolejności
Ułóż poprawną składnię wywołania useActionState:
- Klikanie w kolejności
Ułóż poprawną składnię formularza z Server Action:
- Edytor kodu
W pliku App.jsx jest formularz rejestracji członka załogi (pola name, role i experience) oraz akcja registerCrewMember, która sprawdza dane po stronie „serwera” i zwraca obiekt errors z komunikatem dla każdego złego pola. Uzupełnij luki: ___BLANK1___ to warunek prawdziwy, gdy imię values.name ma mniej niż 2 znaki, ___BLANK2___ to obiekt z wpisanymi wartościami, który akcja zwraca razem z błędami, a ___BLANK3___ to klucz w state.errors z komunikatem dla pola roli. React po zakończeniu akcji czyści pola formularza, dlatego defaultValue przywraca je ze stanu. Błędne dane mają pokazać osobny komunikat przy każdym złym polu i zostawić wpisane wartości, a poprawne powitanie z imieniem.