Odemkněte sílu hooku useActionState v Reactu. Zjistěte, jak zjednodušuje správu formulářů, zpracovává stavy čekání a vylepšuje uživatelský prožitek na praktických a podrobných příkladech.
React useActionState: Komplexní průvodce moderní správou formulářů
Svět webového vývoje se neustále vyvíjí a ekosystém Reactu je v čele této změny. S nedávnými verzemi React představil výkonné funkce, které zásadně zlepšují způsob, jakým vytváříme interaktivní a odolné aplikace. Mezi nejvlivnější z nich patří hook useActionState, který mění pravidla hry při zpracování formulářů a asynchronních operací. Tento hook, dříve známý jako useFormState v experimentálních verzích, je nyní stabilním a nepostradatelným nástrojem pro každého moderního vývojáře v Reactu.
Tento komplexní průvodce vás provede hlubokým ponorem do useActionState. Prozkoumáme problémy, které řeší, jeho základní mechaniku a jak ho využít společně s doplňkovými hooky jako useFormStatus k vytvoření špičkového uživatelského prožitku. Ať už vytváříte jednoduchý kontaktní formulář nebo složitou, datově náročnou aplikaci, porozumění useActionState učiní váš kód čistším, deklarativnějším a robustnějším.
Problém: Složitost tradiční správy stavu formulářů
Než dokážeme ocenit eleganci useActionState, musíme nejprve pochopit výzvy, které řeší. Po léta zahrnovala správa stavu formulářů v Reactu předvídatelný, ale často těžkopádný vzor s použitím hooku useState.
Podívejme se na běžný scénář: jednoduchý formulář pro přidání nového produktu do seznamu. Musíme spravovat několik částí stavu:
- Hodnotu vstupu pro název produktu.
- Stav načítání nebo čekání (loading/pending), aby uživatel dostal zpětnou vazbu během volání API.
- Chybový stav pro zobrazení zpráv, pokud odeslání selže.
- Stav úspěchu nebo zprávu po dokončení.
Typická implementace by mohla vypadat nějak takto:
Příklad: 'Starý způsob' s několika hooky useState
// Fiktivní funkce API
const addProductAPI = async (productName) => {
await new Promise(resolve => setTimeout(resolve, 1500));
if (!productName || productName.length < 3) {
throw new Error('Název produktu musí mít alespoň 3 znaky.');
}
console.log(`Produkt "${productName}" byl přidán.`);
return { success: true };
};
// Komponenta
{error}import { useState } from 'react';
function OldProductForm() {
const [productName, setProductName] = useState('');
const [error, setError] = useState(null);
const [isPending, setIsPending] = useState(false);
const handleSubmit = async (event) => {
event.preventDefault();
setIsPending(true);
setError(null);
try {
await addProductAPI(productName);
setProductName(''); // Vyčistit vstup při úspěchu
} catch (err) {
setError(err.message);
} finally {
setIsPending(false);
}
};
return (
id="productName"
name="productName"
value={productName}
onChange={(e) => setProductName(e.target.value)}
/>
{isPending ? 'Přidávám...' : 'Přidat produkt'}
{error &&
);
}
Tento přístup funguje, ale má několik nevýhod:
- Opakující se kód (Boilerplate): Potřebujeme tři samostatná volání useState pro správu toho, co je koncepčně jediný proces odeslání formuláře.
- Ruční správa stavu: Vývojář je zodpovědný za ruční nastavení a resetování stavů načítání a chyb ve správném pořadí v bloku try...catch...finally. To je repetitivní a náchylné k chybám.
- Těsné propojení (Coupling): Logika pro zpracování výsledku odeslání formuláře je úzce spjata s logikou vykreslování komponenty.
Představujeme useActionState: Změna paradigmatu
useActionState je React hook navržený speciálně pro správu stavu asynchronní akce, jako je odeslání formuláře. Zjednodušuje celý proces tím, že stav přímo propojuje s výsledkem funkce akce.
Jeho signatura je jasná a stručná:
const [state, formAction] = useActionState(actionFn, initialState);
Rozeberme si jeho součásti:
actionFn(previousState, formData)
: Toto je vaše asynchronní funkce, která provádí práci (např. volá API). Jako argumenty přijímá předchozí stav a data formuláře. Klíčové je, že to, co tato funkce vrátí, se stane novým stavem.initialState
: Toto je hodnota stavu před prvním spuštěním akce.state
: Toto je aktuální stav. Zpočátku obsahuje initialState a po každém spuštění se aktualizuje na návratovou hodnotu vaší actionFn.formAction
: Toto je nová, obalená verze vaší funkce akce. Tuto funkci byste měli předat do<form>
elementu jako propaction
. React používá tuto obalenou funkci ke sledování stavu čekání (pending state) akce.
Praktický příklad: Refaktoring s useActionState
Nyní si refaktorujme náš formulář na produkty pomocí useActionState. Zlepšení je okamžitě patrné.
Nejprve musíme přizpůsobit naši logiku akce. Místo vyhazování chyb by akce měla vrátit objekt stavu, který popisuje výsledek.
Příklad: 'Nový způsob' s useActionState
// Funkce akce, navržená pro práci s useActionState
const addProductAction = async (previousState, formData) => {
const productName = formData.get('productName');
await new Promise(resolve => setTimeout(resolve, 1500)); // Simulace síťového zpoždění
if (!productName || productName.length < 3) {
return { message: 'Název produktu musí mít alespoň 3 znaky.', success: false };
}
console.log(`Produkt "${productName}" byl přidán.`);
// Při úspěchu vraťte úspěšnou zprávu a vyčistěte formulář.
return { message: `Úspěšně přidán produkt "${productName}"`, success: true };
};
// Refaktorovaná komponenta
{state.message} {state.message}import { useActionState } from 'react';
// Poznámka: V další části přidáme useFormStatus pro zpracování stavu čekání.
function NewProductForm() {
const initialState = { message: null, success: false };
const [state, formAction] = useActionState(addProductAction, initialState);
return (
{!state.success && state.message && (
)}
{state.success && state.message && (
)}
);
}
Podívejte se, o kolik je to čistší! Nahradili jsme tři hooky useState jediným hookem useActionState. Odpovědností komponenty je nyní čistě vykreslit UI na základě objektu `state`. Veškerá business logika je úhledně zapouzdřena ve funkci `addProductAction`. Stav se automaticky aktualizuje podle toho, co akce vrátí.
Ale počkat, co stav čekání (pending state)? Jak zakážeme tlačítko, když se formulář odesílá?
Zpracování stavů čekání s useFormStatus
React poskytuje doprovodný hook, useFormStatus, navržený k řešení přesně tohoto problému. Poskytuje informace o stavu posledního odeslání formuláře, ale s klíčovým pravidlem: musí být volán z komponenty, která je vykreslena uvnitř <form>
, jehož stav chcete sledovat.
To podporuje čisté oddělení odpovědností. Vytvoříte komponentu speciálně pro prvky UI, které potřebují znát stav odesílání formuláře, jako je například odesílací tlačítko.
Hook useFormStatus vrací objekt s několika vlastnostmi, z nichž nejdůležitější je `pending`.
const { pending, data, method, action } = useFormStatus();
pending
: Booleovská hodnota, která je `true`, pokud se nadřazený formulář právě odesílá, a `false` v opačném případě.data
: Objekt `FormData` obsahující odesílaná data.method
: Řetězec udávající HTTP metodu (`'get'` nebo `'post'`).action
: Odkaz na funkci předanou do `action` propu formuláře.
Vytvoření odesílacího tlačítka, které si je vědomo stavu
Vytvořme si dedikovanou komponentu `SubmitButton` a integrujme ji do našeho formuláře.
Příklad: Komponenta SubmitButton
import { useFormStatus } from 'react-dom';
// Poznámka: useFormStatus se importuje z 'react-dom', nikoli z 'react'.
function SubmitButton() {
const { pending } = useFormStatus();
return (
{pending ? 'Přidávám...' : 'Přidat produkt'}
);
}
Nyní můžeme aktualizovat naši hlavní komponentu formuláře, aby ji používala.
Příklad: Kompletní formulář s useActionState a useFormStatus
{state.message} {state.message}import { useActionState } from 'react';
import { useFormStatus } from 'react-dom';
// ... (funkce addProductAction zůstává stejná)
function SubmitButton() { /* ... jak je definováno výše ... */ }
function CompleteProductForm() {
const initialState = { message: null, success: false };
const [state, formAction] = useActionState(addProductAction, initialState);
return (
{/* Můžeme přidat klíč pro resetování vstupu při úspěchu */}
{!state.success && state.message && (
)}
{state.success && state.message && (
)}
);
}
S touto strukturou nemusí komponenta `CompleteProductForm` vědět nic o stavu čekání. `SubmitButton` je zcela soběstačný. Tento kompoziční vzor je neuvěřitelně silný pro budování složitých a udržovatelných UI.
Síla progresivního vylepšení (Progressive Enhancement)
Jednou z nejhlubších výhod tohoto nového přístupu založeného na akcích, zejména při použití se Server Actions, je automatické progresivní vylepšení. To je životně důležitý koncept pro tvorbu aplikací pro globální publikum, kde mohou být síťové podmínky nespolehlivé a uživatelé mohou mít starší zařízení nebo vypnutý JavaScript.
Funguje to takto:
- Bez JavaScriptu: Pokud prohlížeč uživatele nespustí klientský JavaScript, `<form action={...}>` funguje jako standardní HTML formulář. Provede požadavek na server s plným načtením stránky. Pokud používáte framework jako Next.js, spustí se serverová akce a framework znovu vykreslí celou stránku s novým stavem (např. zobrazí chybu validace). Aplikace je plně funkční, jen bez plynulosti SPA.
- S JavaScriptem: Jakmile se načte balíček JavaScriptu a React hydratuje stránku, stejná `formAction` se provede na straně klienta. Místo úplného znovunačtení stránky se chová jako typický fetch požadavek. Akce je zavolána, stav je aktualizován a pouze nezbytné části komponenty se znovu vykreslí.
To znamená, že logiku formuláře napíšete jednou a funguje bezproblémově v obou scénářích. Standardně tak vytváříte odolnou a přístupnou aplikaci, což je obrovská výhra pro uživatelský prožitek po celém světě.
Pokročilé vzory a případy použití
1. Server Actions vs. Client Actions
Funkce `actionFn`, kterou předáváte do useActionState, může být standardní klientská asynchronní funkce (jako v našich příkladech) nebo Server Action. Server Action je funkce definovaná na serveru, kterou lze volat přímo z klientských komponent. V frameworcích jako Next.js ji definujete přidáním direktivy "use server";
na začátek těla funkce.
- Klientské akce (Client Actions): Ideální pro mutace, které ovlivňují pouze stav na straně klienta nebo volají API třetích stran přímo z klienta.
- Serverové akce (Server Actions): Perfektní pro mutace, které zahrnují databázi nebo jiné serverové zdroje. Zjednodušují vaši architekturu tím, že eliminují potřebu ručně vytvářet API endpointy pro každou mutaci.
Krása je v tom, že useActionState funguje identicky s oběma. Můžete zaměnit klientskou akci za serverovou bez změny kódu komponenty.
2. Optimistické aktualizace s `useOptimistic`
Pro ještě citlivější pocit můžete kombinovat useActionState s hookem useOptimistic. Optimistická aktualizace je, když okamžitě aktualizujete UI za *předpokladu*, že asynchronní akce bude úspěšná. Pokud selže, vrátíte UI do předchozího stavu.
Představte si aplikaci sociálních médií, kde přidáváte komentář. Optimisticky byste nový komentář okamžitě zobrazili v seznamu, zatímco se požadavek odesílá na server. useOptimistic je navržen tak, aby úzce spolupracoval s akcemi a usnadnil implementaci tohoto vzoru.
3. Resetování formuláře po úspěchu
Běžným požadavkem je vymazání vstupů formuláře po úspěšném odeslání. S useActionState toho lze dosáhnout několika způsoby.
- Trik s prop `key`: Jak je ukázáno v našem příkladu `CompleteProductForm`, můžete přiřadit unikátní `key` vstupu nebo celému formuláři. Když se klíč změní, React odpojí starou komponentu a připojí novou, čímž efektivně resetuje její stav. Propojení klíče s příznakem úspěchu (`key={state.success ? 'success' : 'initial'}`) je jednoduchá a účinná metoda.
- Řízené komponenty (Controlled Components): V případě potřeby můžete stále používat řízené komponenty. Spravováním hodnoty vstupu pomocí useState můžete zavolat setter funkci pro její vymazání uvnitř useEffect, který naslouchá stavu úspěchu z useActionState.
Běžné nástrahy a osvědčené postupy
- Umístění
useFormStatus
: Pamatujte, že komponenta volající useFormStatus musí být vykreslena jako potomek `<form>`. Nebude fungovat, pokud je sourozencem nebo rodičem. - Serializovatelný stav: Při použití Server Actions musí být objekt stavu vrácený z vaší akce serializovatelný. To znamená, že nemůže obsahovat funkce, Symboly nebo jiné neserializovatelné hodnoty. Držte se prostých objektů, polí, řetězců, čísel a booleovských hodnot.
- Nevyhazujte chyby v akcích: Místo `throw new Error()`, by vaše funkce akce měla elegantně zpracovat chyby a vrátit objekt stavu, který chybu popisuje (např. `{ success: false, message: 'Došlo k chybě' }`). Tím zajistíte, že stav bude vždy aktualizován předvídatelně.
- Definujte jasný tvar stavu: Od začátku si stanovte konzistentní strukturu pro váš objekt stavu. Tvar jako `{ data: T | null, message: string | null, success: boolean, errors: Record
| null }` může pokrýt mnoho případů použití.
useActionState vs. useReducer: Rychlé srovnání
Na první pohled se může useActionState zdát podobný useReducer, protože oba zahrnují aktualizaci stavu na základě předchozího stavu. Slouží však k odlišným účelům.
useReducer
je obecný hook pro správu složitých přechodů stavu na straně klienta. Je spouštěn odesíláním (dispatching) akcí a je ideální pro logiku stavu, která má mnoho možných, synchronních změn stavu (např. složitý vícekrokový průvodce).useActionState
je specializovaný hook navržený pro stav, který se mění v reakci na jednu, typicky asynchronní akci. Jeho primární rolí je integrace s HTML formuláři, Server Actions a concurrentními funkcemi Reactu, jako jsou přechody stavu čekání.
Závěr: Pro odesílání formulářů a asynchronní operace spojené s formuláři je useActionState moderní, účelový nástroj. Pro ostatní složité, klientské stavové automaty zůstává useReducer vynikající volbou.
Závěr: Přijetí budoucnosti formulářů v Reactu
Hook useActionState je více než jen nové API; představuje zásadní posun k robustnějšímu, deklarativnějšímu a na uživatele zaměřenému způsobu zpracování formulářů a datových mutací v Reactu. Jeho přijetím získáte:
- Méně opakujícího se kódu: Jediný hook nahrazuje několik volání useState a ruční orchestraci stavu.
- Integrované stavy čekání: Bezproblémově zpracovávejte načítací UI s doprovodným hookem useFormStatus.
- Zabudované progresivní vylepšení: Pište kód, který funguje s JavaScriptem i bez něj, a zajistěte tak přístupnost a odolnost pro všechny uživatele.
- Zjednodušená komunikace se serverem: Přirozeně se hodí pro Server Actions, což zjednodušuje full-stack vývoj.
Když začínáte nové projekty nebo refaktorujete stávající, zvažte sáhnout po useActionState. Nejenže zlepší váš vývojářský prožitek tím, že váš kód bude čistší a předvídatelnější, ale také vám umožní vytvářet kvalitnější aplikace, které jsou rychlejší, odolnější a přístupnější pro rozmanité globální publikum.