Odkryj hook useActionState w React do usprawnionego zarz膮dzania stanem wyzwalanego przez akcje asynchroniczne. Popraw wydajno艣膰 i do艣wiadczenie u偶ytkownika.
Implementacja React useActionState: Zarz膮dzanie Stanem Oparte na Akcjach
Hook useActionState w React, wprowadzony w ostatnich wersjach, oferuje dopracowane podej艣cie do zarz膮dzania aktualizacjami stanu wynikaj膮cymi z akcji asynchronicznych. To pot臋偶ne narz臋dzie usprawnia proces obs艂ugi mutacji, aktualizacji interfejsu u偶ytkownika (UI) i zarz膮dzania stanami b艂臋d贸w, zw艂aszcza podczas pracy z React Server Components (RSC) i akcjami serwerowymi. Ten przewodnik dog艂臋bnie om贸wi hook useActionState, dostarczaj膮c praktycznych przyk艂ad贸w i najlepszych praktyk implementacji.
Zrozumienie Potrzeby Zarz膮dzania Stanem Opartego na Akcjach
Tradycyjne zarz膮dzanie stanem w React cz臋sto wi膮偶e si臋 z osobnym zarz膮dzaniem stanami 艂adowania i b艂臋d贸w wewn膮trz komponent贸w. Gdy akcja (np. wys艂anie formularza, pobranie danych) wywo艂uje aktualizacj臋 stanu, deweloperzy zazwyczaj zarz膮dzaj膮 tymi stanami za pomoc膮 wielu wywo艂a艅 useState i potencjalnie z艂o偶onej logiki warunkowej. useActionState dostarcza czystsze i bardziej zintegrowane rozwi膮zanie.
Rozwa偶my prosty scenariusz wysy艂ania formularza. Bez useActionState, mogliby艣my mie膰:
- Zmienn膮 stanu dla danych formularza.
- Zmienn膮 stanu do 艣ledzenia, czy formularz jest wysy艂any (stan 艂adowania).
- Zmienn膮 stanu do przechowywania komunikat贸w o b艂臋dach.
Takie podej艣cie mo偶e prowadzi膰 do rozwlek艂ego kodu i potencjalnych niesp贸jno艣ci. useActionState konsoliduje te zagadnienia w jednym hooku, upraszczaj膮c logik臋 i poprawiaj膮c czytelno艣膰 kodu.
Wprowadzenie do useActionState
Hook useActionState przyjmuje dwa argumenty:
- Asynchroniczn膮 funkcj臋 ("akcj臋"), kt贸ra wykonuje aktualizacj臋 stanu. Mo偶e to by膰 akcja serwerowa lub dowolna funkcja asynchroniczna.
- Pocz膮tkow膮 warto艣膰 stanu.
Zwraca tablic臋 zawieraj膮c膮 dwa elementy:
- Bie偶膮c膮 warto艣膰 stanu.
- Funkcj臋 do wywo艂ania akcji. Ta funkcja automatycznie zarz膮dza stanami 艂adowania i b艂臋d贸w zwi膮zanymi z akcj膮.
Oto podstawowy przyk艂ad:
import { useActionState } from 'react';
async function updateServer(prevState, formData) {
// Symulacja asynchronicznej aktualizacji serwera.
await new Promise(resolve => setTimeout(resolve, 1000));
const data = Object.fromEntries(formData);
if (data.name === "error") {
return 'Nie uda艂o si臋 zaktualizowa膰 serwera.';
}
return `Zaktualizowano imi臋 na: ${data.name}`;
}
function MyComponent() {
const [state, dispatch] = useActionState(updateServer, 'Stan Pocz膮tkowy');
async function handleSubmit(event) {
event.preventDefault();
const formData = new FormData(event.target);
const result = await dispatch(formData);
console.log(result);
}
return (
);
}
W tym przyk艂adzie:
updateServerto asynchroniczna akcja, kt贸ra symuluje aktualizacj臋 serwera. Otrzymuje poprzedni stan oraz dane z formularza.useActionStateinicjalizuje stan warto艣ci膮 'Stan Pocz膮tkowy' i zwraca bie偶膮cy stan oraz funkcj臋dispatch.- Funkcja
handleSubmitwywo艂ujedispatchz danymi formularza.useActionStateautomatycznie obs艂uguje stany 艂adowania i b艂臋d贸w podczas wykonywania akcji.
Obs艂uga Stan贸w 艁adowania i B艂臋d贸w
Jedn膮 z kluczowych zalet useActionState jest wbudowane zarz膮dzanie stanami 艂adowania i b艂臋d贸w. Funkcja dispatch zwraca obietnic臋 (promise), kt贸ra rozwi膮zuje si臋 z wynikiem akcji. Je艣li akcja zg艂osi b艂膮d, obietnica zostanie odrzucona z tym b艂臋dem. Mo偶na to wykorzysta膰 do odpowiedniej aktualizacji interfejsu u偶ytkownika.
Zmodyfikujmy poprzedni przyk艂ad, aby wy艣wietla膰 komunikat o 艂adowaniu i komunikat o b艂臋dzie:
import { useActionState } from 'react';
import { useState } from 'react';
async function updateServer(prevState, formData) {
// Symulacja asynchronicznej aktualizacji serwera.
await new Promise(resolve => setTimeout(resolve, 1000));
const data = Object.fromEntries(formData);
if (data.name === "error") {
throw new Error('Nie uda艂o si臋 zaktualizowa膰 serwera.');
}
return `Zaktualizowano imi臋 na: ${data.name}`;
}
function MyComponent() {
const [state, dispatch] = useActionState(updateServer, 'Stan Pocz膮tkowy');
const [isSubmitting, setIsSubmitting] = useState(false);
const [errorMessage, setErrorMessage] = useState(null);
async function handleSubmit(event) {
event.preventDefault();
const formData = new FormData(event.target);
setIsSubmitting(true);
setErrorMessage(null);
try {
const result = await dispatch(formData);
console.log(result);
} catch (error) {
console.error("B艂膮d podczas wysy艂ania:", error);
setErrorMessage(error.message);
} finally {
setIsSubmitting(false);
}
}
return (
);
}
Kluczowe zmiany:
- Dodali艣my zmienne stanu
isSubmittingierrorMessagedo 艣ledzenia stan贸w 艂adowania i b艂臋d贸w. - W
handleSubmit, ustawiamyisSubmittingnatrueprzed wywo艂aniemdispatchi 艂apiemy wszelkie b艂臋dy, aby zaktualizowa膰errorMessage. - Wy艂膮czamy przycisk wysy艂ania podczas przesy艂ania i warunkowo wy艣wietlamy komunikaty o 艂adowaniu i b艂臋dach.
useActionState z Akcjami Serwerowymi w React Server Components (RSC)
useActionState pokazuje swoj膮 si艂臋, gdy jest u偶ywany z React Server Components (RSC) i akcjami serwerowymi. Akcje serwerowe to funkcje, kt贸re dzia艂aj膮 na serwerze i mog膮 bezpo艣rednio modyfikowa膰 藕r贸d艂a danych. Pozwalaj膮 one na wykonywanie operacji po stronie serwera bez pisania punkt贸w ko艅cowych API.
Uwaga: Ten przyk艂ad wymaga 艣rodowiska React skonfigurowanego do obs艂ugi Server Components i Server Actions.
// app/actions.js (Akcja Serwerowa)
'use server';
import { cookies } from 'next/headers'; //Przyk艂ad dla Next.js
export async function updateName(prevState, formData) {
const name = formData.get('name');
if (!name) {
return 'Prosz臋 poda膰 imi臋.';
}
try {
// Symulacja aktualizacji bazy danych.
await new Promise(resolve => setTimeout(resolve, 1000));
cookies().set('userName', name);
return `Zaktualizowano imi臋 na: ${name}`; //Sukces!
} catch (error) {
console.error("Aktualizacja bazy danych nie powiod艂a si臋:", error);
return 'Nie uda艂o si臋 zaktualizowa膰 imienia.'; // Wa偶ne: Zwr贸膰 komunikat, a nie rzucaj b艂臋du (Error)
}
}
// app/page.jsx (React Server Component)
'use client';
import { useActionState } from 'react';
import { updateName } from './actions';
function MyComponent() {
const [state, dispatch] = useActionState(updateName, 'Stan Pocz膮tkowy');
async function handleSubmit(event) {
event.preventDefault();
const formData = new FormData(event.target);
const result = await dispatch(formData);
console.log(result);
}
return (
);
}
export default MyComponent;
W tym przyk艂adzie:
updateNameto akcja serwerowa zdefiniowana wapp/actions.js. Otrzymuje ona poprzedni stan i dane z formularza, aktualizuje baz臋 danych (symulacja) i zwraca komunikat o sukcesie lub b艂臋dzie. Co kluczowe, akcja zwraca komunikat, a nie rzuca b艂臋du. Akcje Serwerowe preferuj膮 zwracanie informacyjnych komunikat贸w.- Komponent jest oznaczony jako komponent kliencki (
'use client'), aby m贸c u偶ywa膰 hookauseActionState. - Funkcja
handleSubmitwywo艂ujedispatchz danymi z formularza.useActionStateautomatycznie zarz膮dza aktualizacj膮 stanu na podstawie wyniku akcji serwerowej.
Wa偶ne Kwestie Dotycz膮ce Akcji Serwerowych
- Obs艂uga B艂臋d贸w w Akcjach Serwerowych: Zamiast rzuca膰 b艂臋dy, zwracaj znacz膮cy komunikat o b艂臋dzie z Akcji Serwerowej.
useActionStatepotraktuje ten komunikat jako nowy stan. Pozwala to na eleganck膮 obs艂ug臋 b艂臋d贸w po stronie klienta. - Optymistyczne Aktualizacje: Akcje serwerowe mog膮 by膰 u偶ywane z optymistycznymi aktualizacjami w celu poprawy postrzeganej wydajno艣ci. Mo偶esz natychmiast zaktualizowa膰 interfejs u偶ytkownika i cofn膮膰 zmiany, je艣li akcja si臋 nie powiedzie.
- Ponowna Walidacja: Po udanej mutacji rozwa偶 ponown膮 walidacj臋 buforowanych danych, aby upewni膰 si臋, 偶e interfejs u偶ytkownika odzwierciedla najnowszy stan.
Zaawansowane Techniki z useActionState
1. U偶ycie Reducera do Z艂o偶onych Aktualizacji Stanu
W przypadku bardziej z艂o偶onej logiki stanu, mo偶na po艂膮czy膰 useActionState z funkcj膮 reducera. Pozwala to zarz膮dza膰 aktualizacjami stanu w przewidywalny i 艂atwy do utrzymania spos贸b.
import { useActionState } from 'react';
import { useReducer } from 'react';
const initialState = {
count: 0,
message: 'Stan Pocz膮tkowy',
};
function reducer(state, action) {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + 1 };
case 'DECREMENT':
return { ...state, count: state.count - 1 };
case 'SET_MESSAGE':
return { ...state, message: action.payload };
default:
return state;
}
}
async function updateState(state, action) {
// Symulacja operacji asynchronicznej.
await new Promise(resolve => setTimeout(resolve, 500));
switch (action.type) {
case 'INCREMENT':
return reducer(state, action);
case 'DECREMENT':
return reducer(state, action);
case 'SET_MESSAGE':
return reducer(state, action);
default:
return state;
}
}
function MyComponent() {
const [state, dispatch] = useActionState(updateState, initialState);
return (
Licznik: {state.count}
Wiadomo艣膰: {state.message}
);
}
2. Optymistyczne Aktualizacje z useActionState
Optymistyczne aktualizacje poprawiaj膮 do艣wiadczenie u偶ytkownika, natychmiast aktualizuj膮c interfejs u偶ytkownika, tak jakby akcja zako艅czy艂a si臋 sukcesem, a nast臋pnie cofaj膮c aktualizacj臋, je艣li akcja si臋 nie powiedzie. Dzi臋ki temu aplikacja mo偶e wydawa膰 si臋 bardziej responsywna.
import { useActionState } from 'react';
import { useState } from 'react';
async function updateServer(prevState, formData) {
// Symulacja asynchronicznej aktualizacji serwera.
await new Promise(resolve => setTimeout(resolve, 1000));
const data = Object.fromEntries(formData);
if (data.name === "error") {
throw new Error('Nie uda艂o si臋 zaktualizowa膰 serwera.');
}
return `Zaktualizowano imi臋 na: ${data.name}`;
}
function MyComponent() {
const [name, setName] = useState('Imi臋 Pocz膮tkowe');
const [state, dispatch] = useActionState(async (prevName, newName) => {
try {
const result = await updateServer(prevName, {
name: newName,
});
return newName; // Aktualizuj w przypadku sukcesu
} catch (error) {
// Wycofaj w przypadku b艂臋du
console.error("Aktualizacja nie powiod艂a si臋:", error);
setName(prevName);
return prevName;
}
}, name);
async function handleSubmit(event) {
event.preventDefault();
const formData = new FormData(event.target);
const newName = formData.get('name');
setName(newName); // Optymistycznie zaktualizuj UI
await dispatch(newName);
}
return (
);
}
3. Debouncing Akcji
W niekt贸rych scenariuszach mo偶na chcie膰 zastosowa膰 debouncing akcji, aby zapobiec ich zbyt cz臋stemu wywo艂ywaniu. Mo偶e to by膰 przydatne w przypadku takich scenariuszy jak pola wyszukiwania, gdzie chcemy wywo艂a膰 akcj臋 dopiero po tym, jak u偶ytkownik przestanie pisa膰 przez okre艣lony czas.
import { useActionState } from 'react';
import { useState, useEffect } from 'react';
async function searchItems(prevState, query) {
// Symulacja asynchronicznego wyszukiwania.
await new Promise(resolve => setTimeout(resolve, 500));
return `Wyniki wyszukiwania dla: ${query}`;
}
function MyComponent() {
const [query, setQuery] = useState('');
const [state, dispatch] = useActionState(searchItems, 'Stan Pocz膮tkowy');
useEffect(() => {
const timeoutId = setTimeout(() => {
if (query) {
dispatch(query);
}
}, 300); // Debounce na 300ms
return () => clearTimeout(timeoutId);
}, [query, dispatch]);
return (
setQuery(e.target.value)}
/>
Stan: {state}
);
}
Najlepsze Praktyki dla useActionState
- Zachowaj Czysto艣膰 Akcji: Upewnij si臋, 偶e Twoje akcje s膮 czystymi funkcjami (lub tak blisko, jak to mo偶liwe). Nie powinny mie膰 efekt贸w ubocznych innych ni偶 aktualizacja stanu.
- Elegancka Obs艂uga B艂臋d贸w: Zawsze obs艂uguj b艂臋dy w swoich akcjach i dostarczaj u偶ytkownikowi informacyjne komunikaty o b艂臋dach. Jak wspomniano wy偶ej w przypadku Akcji Serwerowych, preferuj zwracanie ci膮gu znak贸w z komunikatem o b艂臋dzie z akcji serwerowej, zamiast rzucania b艂臋du.
- Optymalizuj Wydajno艣膰: B膮d藕 艣wiadomy implikacji wydajno艣ciowych swoich akcji, zw艂aszcza przy pracy z du偶ymi zbiorami danych. Rozwa偶 u偶ycie technik memoizacji, aby unika膰 niepotrzebnych ponownych renderowa艅.
- Pami臋taj o Dost臋pno艣ci: Upewnij si臋, 偶e Twoja aplikacja pozostaje dost臋pna dla wszystkich u偶ytkownik贸w, w tym os贸b z niepe艂nosprawno艣ciami. Zapewnij odpowiednie atrybuty ARIA i nawigacj臋 za pomoc膮 klawiatury.
- Dok艂adne Testowanie: Pisz testy jednostkowe i integracyjne, aby upewni膰 si臋, 偶e Twoje akcje i aktualizacje stanu dzia艂aj膮 poprawnie.
- Internacjonalizacja (i18n): W przypadku globalnych aplikacji zaimplementuj i18n, aby wspiera膰 wiele j臋zyk贸w i kultur.
- Lokalizacja (l10n): Dostosuj swoj膮 aplikacj臋 do okre艣lonych region贸w, dostarczaj膮c zlokalizowan膮 tre艣膰, formaty dat i symbole walut.
useActionState kontra Inne Rozwi膮zania do Zarz膮dzania Stanem
Chocia偶 useActionState zapewnia wygodny spos贸b zarz膮dzania aktualizacjami stanu opartymi na akcjach, nie jest zamiennikiem dla wszystkich rozwi膮za艅 do zarz膮dzania stanem. W przypadku z艂o偶onych aplikacji z globalnym stanem, kt贸ry musi by膰 wsp贸艂dzielony mi臋dzy wieloma komponentami, biblioteki takie jak Redux, Zustand czy Jotai mog膮 by膰 bardziej odpowiednie.
Kiedy u偶ywa膰 useActionState:
- Aktualizacje stanu o prostej do umiarkowanej z艂o偶ono艣ci.
- Aktualizacje stanu 艣ci艣le powi膮zane z akcjami asynchronicznymi.
- Integracja z React Server Components i Akcjami Serwerowymi.
Kiedy rozwa偶y膰 inne rozwi膮zania:
- Z艂o偶one zarz膮dzanie stanem globalnym.
- Stan, kt贸ry musi by膰 wsp贸艂dzielony przez du偶膮 liczb臋 komponent贸w.
- Zaawansowane funkcje, takie jak debugowanie w czasie (time-travel debugging) czy middleware.
Podsumowanie
Hook useActionState w React oferuje pot臋偶ny i elegancki spos贸b zarz膮dzania aktualizacjami stanu wyzwalanymi przez akcje asynchroniczne. Poprzez konsolidacj臋 stan贸w 艂adowania i b艂臋d贸w, upraszcza kod i poprawia czytelno艣膰, szczeg贸lnie podczas pracy z React Server Components i akcjami serwerowymi. Zrozumienie jego mocnych stron i ogranicze艅 pozwala wybra膰 odpowiednie podej艣cie do zarz膮dzania stanem w Twojej aplikacji, co prowadzi do 艂atwiejszego w utrzymaniu i bardziej wydajnego kodu.
Post臋puj膮c zgodnie z najlepszymi praktykami opisanymi w tym przewodniku, mo偶esz skutecznie wykorzysta膰 useActionState do poprawy do艣wiadczenia u偶ytkownika i przep艂ywu pracy deweloperskiej w swojej aplikacji. Pami臋taj, aby wzi膮膰 pod uwag臋 z艂o偶ono艣膰 swojej aplikacji i wybra膰 rozwi膮zanie do zarz膮dzania stanem, kt贸re najlepiej odpowiada Twoim potrzebom. Od prostych wysy艂ek formularzy po z艂o偶one mutacje danych, useActionState mo偶e by膰 cennym narz臋dziem w Twoim arsenale deweloperskim React.