useState
useState
jest hookiem reactowym, który pozwala na dodanie do komponentu zmiennej stanu.
const [state, setState] = useState(initialState)
- Dokumentacja
- Sposób użycia
- Znane problemy
- Aktualizuję wartość stanu, ale w konsoli wyświetla mi się stan poprzedni
- Aktualizuję wartość stanu, ale ekran się nie odświeża
- Dostaję błąd: “Too many re-renders”
- Moja funkcja inicjalizująca lub aktualizująca jest uruchamiana dwa razy
- Próbuję zapisać w stanie funkcję, ale zamiast tego moja funkcja jest wywoływana
Dokumentacja
useState(initialState)
Wywołaj useState
na głównym poziomie komponentu, aby zadeklarować zmienną stanu.
import { useState } from 'react';
function MyComponent() {
const [age, setAge] = useState(28);
const [name, setName] = useState('Taylor');
const [todos, setTodos] = useState(() => createTodos());
// ...
Przyjęło się nazywać stan [something, setSomething]
, używając przy tym składni destrukturyzacji tablicy.
Więcej przykładów znajdziesz powyżej.
Parametry
initialState
: Wartość, jaką stan ma otrzymać na początku. Może być dowolnego typu, jednak dla funkcji przewidziane jest specjalne zachowanie. Ten argument jest ignorowany po pierwszym renderowaniu komponentu.- Jeśli jako argument
initialState
przekażesz funkcję, będzie ona traktowana jako funkcja inicjalizująca. Musi być “czysta”, nie może przyjmować żadnych argumentów i powinna zwracać wartość dla zmiennej stanu. React wywoła twoją funkcję inicjalizującą podczas tworzenia komponentu i przypisze zwróconą przez nią wartość jako stan początkowy. Zobacz przykład powyżej.
- Jeśli jako argument
Zwracana wartość
useState
zwraca tablicę o dokładnie dwóch elementach:
- Aktualna wartość stanu. Podczas pierwszego renderowania będzie taka sama jak przekazany do hooka argument
initialState
. - Funkcja
set
, która umożliwia zaktualizowanie stanu do innej wartości i wymusza przerenderowanie komponentu.
Zastrzeżenia
useState
jest hookiem, więc można go wywoływać tylko na głównym poziomie komponentu lub innego hooka. Nie można go wywołać w pętli lub instrukcji warunkowej. Jeśli masz sytuację, która wymaga pętli lub warunku, stwórz nowy komponent i przenieś do niego ten stan.- W Trybie Restrykcyjnym (ang. Strict Mode) React wywoła twoją funkcję inicjalizującą dwukrotnie, aby pomóc ci w zlokalizowaniu niechcianych “nieczystości”. To zachowanie tyczy się tylko środowiska deweloperskiego i nie wpływa na produkcję. Jeśli twoja funkcja inicjalizująca jest “czysta” (a powinna być), nie wpłynie to w żaden sposób na logikę twojego komponentu. Wynik z jednego z wywołań tej funkcji zostanie zwyczajnie zignorowany.
Funkcja set
, np. setSomething(nextState)
Funkcja set
zwracana przez useState
pozwala na zaktualizowanie stanu do innej wartości i wymusza przerenderowanie komponentu. Nową wartość stanu można przekazać bezpośrednio lub można przekazać funkcję, która wyliczy nowy stan na podstawie poprzedniego:
const [name, setName] = useState('Edward');
const [age, setAge] = useState(42);
function handleClick() {
setName('Taylor');
setAge(a => a + 1);
// ...
Parametry
Note that if you call a set
function while rendering, it must be inside a condition like prevCount !== count
, and there must be a call like setPrevCount(count)
inside of the condition. Otherwise, your component would re-render in a loop until it crashes. Also, you can only update the state of the currently rendering component like this. Calling the set
function of another component during rendering is an error. Finally, your set
call should still update state without mutation — this doesn’t mean you can break other rules of pure functions.
nextState
: Wartość, na jaką chcesz zmienić stan. Może być dowolnego typu, jednak dla funkcji przewidziane jest specjalne zachowanie.- Jeśli jako argument
nextState
przekażesz funkcję, będzie ona traktowana jako funkcja aktualizująca. Musi być “czysta”, powinna przyjmować poprzedni stan jako swój jedyny argument i powinna zwracać następną wartość stanu. React umieści twoją funkcję aktualizującą w kolejce i przerenderuje komponent. Podczas kolejnego renderowania React obliczy nowy stan, aplikując kolejno wszystkie zakolejkowane funkcje aktualizujące na poprzednim stanie. Zobacz przykład powyżej.
- Jeśli jako argument
Zwracana wartość
Funkcje set
nie zwracają żadnej wartości.
Zastrzeżenia
-
Funkcja
set
aktualizuje zmienną stanu tylko dla następnego renderowania. Jeśli spróbujesz odczytać wartość stanu tuż po wywołaniu funkcjiset
, otrzymasz starą wartość, która istniała przed wywołaniem. -
Jeśli nowa wartość i aktualny stan są identyczne (na podstawie porównania
Object.is
), React nie wymusi ponownego renderowania komponentu i jego potomków. Jest to pewna forma optymalizacji. Mimo że czasem React nadal może wywołać twój komponent ponownie przed pominięciem potomków, nie powinno to wpłynąć na logikę działania komponentu. -
React grupuje aktualizacje stanu. Aktualizuje on ekran po zakończeniu działania wszystkich procedur obsługi zdarzeń i po tym, jak te procedury wywoją odpowiednie funkcje
set
. Zapobiega to wielokrotnemu renderowaniu komponentu podczas pojedynczego zdarzenia. W rzadkich sytuacjach, kiedy chcesz wymusić wcześniejsze zaktualizowanie ekranu, np. aby odczytać coś z DOM, możesz użyć funkcjiflushSync
. -
Wywołanie funkcji
set
podczas renderowania jest dozwolone tylko w ramach aktualnie renderowanego komponentu. React zignoruje wynik aktualnego renderowania i natychmiast spróbuje wyrenderować go ponownie z nowym stanem. Ten wzorzec jest rzadko stosowany, jednak możesz go użyć, aby zapisać dane z poprzedniego renderowania. Zobacz przykład powyżej. -
W Trybie Restrykcyjnym (ang. Strict Mode) React wywoła twoją funkcję aktualizującą dwukrotnie, aby pomóc ci w zlokalizowaniu niechcianych “nieczystości”. To zachowanie tyczy się tylko środowiska deweloperskiego i nie wpływa na produkcję. Jeśli twoja funkcja aktualizująca jest “czysta” (a powinna być), nie wpłynie to w żaden sposób na logikę twojego komponentu. Wynik z jednego z wywołań tej funkcji zostanie zwyczajnie zignorowany.
Sposób użycia
Dodawanie stanu do komponentu
Wywołaj useState
na głównym poziomie komponentu, aby zadeklarować jedną lub więcej zmiennych stanu.
import { useState } from 'react';
function MyComponent() {
const [age, setAge] = useState(42);
const [name, setName] = useState('Taylor');
// ...
Przyjęło się, że zmienne stanu nazywamy [something, setSomething]
, korzystając przy tym z destrukturyzacji tablicy.
useState
zwraca tablicę o dokładnie dwóch elementach:
- Aktualny stan naszej zmiennej stanu, pierwotnie ustawiony na stan początkowy przekazany jako argument.
- Funkcja
set
, która pozwala zmienić wartość stanu na dowolną inną w odpowiedzi na jakąś interakcję.
Aby zaktualizować to, co jest wyświetlane na ekranie, wywołaj funkcję set
, przekazując nowy stan jako argument:
function handleClick() {
setName('Robin');
}
React zapisze nowy stan, wyrenderuje ponownie twój komponent już z nową wartością, a na koniec zaktualizuje UI.
Przykład 1 z 4: Licznik (liczba)
W tym przykładzie zmienna stanu count
przechowuje liczbę. Klikanie na przycisk zwiększa tę wartość.
import { useState } from 'react'; export default function Counter() { const [count, setCount] = useState(0); function handleClick() { setCount(count + 1); } return ( <button onClick={handleClick}> Wciśnięto mnie {count} razy </button> ); }
Aktualizowanie stanu w oparciu o poprzedni stan
Załóżmy, że wartość age
jest obecnie równa 42
. Poniższa procedura obsługi zdarzenia wywołuje setAge(age + 1)
trzykrotnie:
function handleClick() {
setAge(age + 1); // setAge(42 + 1)
setAge(age + 1); // setAge(42 + 1)
setAge(age + 1); // setAge(42 + 1)
}
Mimo to po jednym kliknięciu wartość age
będzie równa 43
, a nie 45
! Dzieje się tak, ponieważ wywoływanie funkcji set
nie aktualizuje zmiennej stanu age
w trakcie wywoływania kodu. Tak więc każde setAge(age + 1)
tak naprawdę jest tym samym, co setAge(43)
.
Aby rozwiązać ten problem *możesz przekazać do setAge
funkcję aktualizującą zamiast samej wartości:
function handleClick() {
setAge(a => a + 1); // setAge(42 => 43)
setAge(a => a + 1); // setAge(43 => 44)
setAge(a => a + 1); // setAge(44 => 45)
}
W tym przykładzie a => a + 1
jest twoją funkcją aktualizującą. Otrzymuje ona aktualny stan i oblicza na jego podstawie następny stan.
React umieszcza funkcje aktualizujące w kolejce. Następnie, podczas kolejnego renderowania, wywołuje je w takiej samej kolejności:
a => a + 1
otrzyma aktualny stan równy42
i zwróci następny stan jako43
.a => a + 1
otrzyma aktualny stan równy43
i zwróci następny stan jako44
.a => a + 1
otrzyma aktualny stan równy44
i zwróci następny stan jako45
.
W tym przypadku nie mamy więcej zakolejkowanych zmian, więc React na koniec zapisze wartość 45
jako aktualny stan.
Przyjęło się, żeby nazywać argument odpowiadający za poprzedni stan używając pierwszej litery nazwy zmiennej stanu, na przykład a
dla age
. Możesz jednak nazwać go dowolnie, np. prevAge
.
React może wywołać twoje funkcje aktualizujące dwukrotnie w środowisku deweloperskim, aby upewnić się, że są one “czyste”.
Dla dociekliwych
W internecie można natknąć się na porady, które radzą zawsze pisać setAge(a => a + 1)
, jeśli następna wartość stanu zależy od poprzedniej. Nie ma w tym nic złego, ale też nie jest to wymagane.
W większości przypadków nie ma różnicy między tymi dwoma podejściami. React zawsze upewnia się, że przy wszelkich intencjonalnych akcjach użytkownika, np. kliknięciach, zmienna stanu age
zostanie zaktualizowana jeszcze przed kolejnym kliknięciem. Oznacza to, że nie ma ryzyka, iż procedura obsługi kliknięcia otrzyma “starą” wartość age
.
Jeśli jednak wykonujesz kilka aktualizacji stanu przy okazji jednego zdarzenia, funkcje aktualizujące mogą okazać się pomocne. Pomagają one również w sytuacjach, kiedy dostęp do zmiennej stanu jest utrudniony (może się tak zdarzyć po wdrożeniu różnych strategii optymalizujących renderowanie).
Jeśli lubisz spójność w kodzie, możesz zawsze używać funkcji aktualizującej, kiedy nowy stan zależy od poprzedniego. Jeśli jednak nowy stan zależy od poprzedniej wartości innej zmiennej stanu, warto zastanowić się nad połączeniem ich w jeden obiekt i użyciem reduktora (ang. reducer).
Przykład 1 z 2: Przekazywanie funkcji aktualizującej
W tym przykładzie przekazujemy funkcję aktualizującą, więc przycisk “+3” zadziała.
import { useState } from 'react'; export default function Counter() { const [age, setAge] = useState(42); function increment() { setAge(a => a + 1); } return ( <> <h1>Twój wiek: {age}</h1> <button onClick={() => { increment(); increment(); increment(); }}>+3</button> <button onClick={() => { increment(); }}>+1</button> </> ); }
Aktualizowanie obiektów i tablic przechowywanych w stanie
W zmiennej stanu możesz przechowywać obiekty i tablice. W Reakcie stan jest “tylko do odczytu”, więc podczas aktualizacji takich zmiennych musisz je zastąpić zamiast modyfikować (mutować). Dla przykładu, jeśli w stanie trzymasz obiekt form
, nie aktualizuj go w ten sposób:
// 🚩 Nie modyfikuj obiektu przechowywanego w stanie:
form.firstName = 'Taylor';
Zamiast tego zastąp cały obiekt poprzez stworzenie całkiem nowego:
// ✅ Zastąp stan nowym obiektem
setForm({
...form,
firstName: 'Taylor'
});
Aby dowiedzieć się więcej na ten temat, przeczytaj rozdziały pt. Aktualizowanie obiektów w stanie i Aktualizowanie tablic w stanie.
Przykład 1 z 4: Formularz (obiekt)
W tym przykładzie zmienna stanu form
przechowuje obiekt. Każda kontrolka formularza ma przypisaną procedurę obsługi zmiany wartości, która wywołuje setForm
z nowym stanem całego formularza. Składnia { ...form }
daje nam pewność, że obiekt w stanie zostanie zastąpiony, a nie tylko zmodyfikowany.
import { useState } from 'react'; export default function Form() { const [form, setForm] = useState({ firstName: 'Barbara', lastName: 'Hepworth', email: 'bhepworth@sculpture.com', }); return ( <> <label> Imię: <input value={form.firstName} onChange={e => { setForm({ ...form, firstName: e.target.value }); }} /> </label> <label> Nazwisko: <input value={form.lastName} onChange={e => { setForm({ ...form, lastName: e.target.value }); }} /> </label> <label> E-mail: <input value={form.email} onChange={e => { setForm({ ...form, email: e.target.value }); }} /> </label> <p> {form.firstName}{' '} {form.lastName}{' '} ({form.email}) </p> </> ); }
Unikanie ponownego tworzenia stanu początkowego
React zapisuje stan początkowy tylko jeden raz, a przy kolejnych renderowaniach zwyczajnie go ignoruje.
function TodoList() {
const [todos, setTodos] = useState(createInitialTodos());
// ...
Mimo że wynik funkcji createInitialTodos()
jest używany tylko podczas pierwszego renderowania, i tak jest ona wywoływana przy każdym kolejnym renderowaniu. Czasami może być to problem, jeśli podczas działania tworzy ona dużą tablicę lub wykonuje kosztowne obliczenia.
Można sobie z tym poradzić przekazując do useState
funkcję inicjalizującą:
function TodoList() {
const [todos, setTodos] = useState(createInitialTodos);
// ...
Zwróć uwagę, że przekazaliśmy tutaj createInitialTodos
, która jest funkcją, a nie createInitialTodos()
, które jest wynikiem jej wywołania. Jeśli do useState
przekażesz jakąś funkcję, React wywoła ją tylko podczas inicjalizacji.
React może wywołać twoją funkcję inicjalizującą dwukrotnie w środowisku deweloperskim, aby sprawdzić, czy jest ona “czysta”.
Przykład 1 z 2: Przekazywanie funkcji inicjalizującej
W tym przykładzie przekazujemy funkcję inicjalizującą, więc createInitialTodos
jest wywoływana tylko podczas inicjalizacji. Nie wywołuje się podczas kolejnych renderowań, np. po wpisaniu tekstu do pola formularza.
import { useState } from 'react'; function createInitialTodos() { const initialTodos = []; for (let i = 0; i < 50; i++) { initialTodos.push({ id: i, text: 'Item ' + (i + 1) }); } return initialTodos; } export default function TodoList() { const [todos, setTodos] = useState(createInitialTodos); const [text, setText] = useState(''); return ( <> <input value={text} onChange={e => setText(e.target.value)} /> <button onClick={() => { setText(''); setTodos([{ id: todos.length, text: text }, ...todos]); }}>Dodaj</button> <ul> {todos.map(item => ( <li key={item.id}> {item.text} </li> ))} </ul> </> ); }
Resetowanie stanu za pomocą właściwości key
W większości przypadków z właściwością key
spotkasz się tylko przy okazji renderowania list. Czasami jednak służy ona do czegoś innego.
Przekazując inną wartość key
do komponentu możesz zresetować jego stan. W poniższym przykładzie przycisk resetujący ustawia zmienną stanu version
, którą możemy przekazać jako właściwość key
do Form
. Kiedy zmieni się key
, React stworzy komponent Form
od nowa (razem ze wszystkimi potomkami), dzięki czemu ich stan zostanie zresetowany.
Aby dowiedzieć się więcej, przeczytaj rozdział pt. Utrzymywanie i resetowanie stanu.
import { useState } from 'react'; export default function App() { const [version, setVersion] = useState(0); function handleReset() { setVersion(version + 1); } return ( <> <button onClick={handleReset}>Resetuj</button> <Form key={version} /> </> ); } function Form() { const [name, setName] = useState('Taylor'); return ( <> <input value={name} onChange={e => setName(e.target.value)} /> <p>Cześć, {name}.</p> </> ); }
Przechowywanie informacji z poprzednich renderowań
Stan zazwyczaj aktualizujemy w procedurach obsługi zdarzeń (ang. event handlers). W rzadkich przypadkach możemy chcieć zmienić stan w odpowiedzi na renderowanie - na przykład, żeby zmienić stan przy zmianie właściwości.
Zwykle jednak nie ma potrzeby tak robić:
- Jeśli wartość może zostać obliczona w całości na podstawie aktualnych właściwości i innej zmiennej stanu, należy całkowicie pozbyć się tego nadmiarowego stanu. Jeśli martwisz się o zbyt częste przeliczanie wartości, skorzystaj z hooka
useMemo
. - Jeśli chcesz zresetować stan całego poddrzewa komponentu, przekaż mu inną wartość
key
. - Jeśli tylko możesz, aktualizuj stan w procedurach obsługi zdarzeń.
Są jednak sytuację, w których żadna z powyższych reguł nie ma zastosowania. Można wtedy aktualizować stan na podstawie wartości, które już zostały wyrenderowane, wywołując funkcję set
w trakcie renderowania komponentu.
Oto przykład. Komponent CountLabel
wyświetla wartość przekazanej do niego właściwości count
:
export default function CountLabel({ count }) {
return <h1>{count}</h1>
}
Załóżmy, że chcesz wyświetlić informację, czy licznik został zwiększony, czy zmniejszony od ostatniej zmiany. Właściwość count
nie mówi ci tego w żaden sposób - musisz zatem jakoś śledzić jej poprzednią wartość. W takiej sytuacji należy dodać kolejne zmienne stanu: jedną prevCount
do śledzenia wartości oraz drugą trend
do przechowywania informacji o kierunku tej zmiany. Teraz wystarczy porównać prevCount
z count
i jeśli nie są równe, zaktualizować zarówno prevCount
, jak i trend
. Dzięki temu możliwe będzie wyświetlenie obydwu wartości oraz określenie, jak zmieniły się one od ostatniego renderowania.
import { useState } from 'react'; export default function CountLabel({ count }) { const [prevCount, setPrevCount] = useState(count); const [trend, setTrend] = useState(null); if (prevCount !== count) { setPrevCount(count); setTrend(count > prevCount ? 'zwiększa się' : 'zmniejsza się'); } return ( <> <h1>{count}</h1> {trend && <p>Licznik {trend}</p>} </> ); }
Zwróć uwagę, że jeśli wywołujesz funkcję set
podczas renderowania, musi się to odbywać w warunku prevCount !== count
, w którym to również wywołujesz setPrevCount(count)
. W przeciwnym wypadku komponent renderowałby się ponownie w nieskończoność, co doprowadziłoby do zawieszenia aplikacji. Pamiętaj, że możesz w ten sposób aktualizować stan tylko aktualnie renderowanego komponentu. Wywoływanie funkcji set
pochodzącej z innego komponentu podczas renderowania byłoby błędem. I wreszcie, pamiętaj, że wywołanie funkcji set
powinno aktualizować stan bez jego mutowania — to, że obsługujemy tu przypadek specjalny, nie oznacza, że możemy łamać inne zasady czystych funkcji.
Powyższy schemat działania może wydawać się trudny do zrozumienia i generalnie lepiej go unikać. Mimo wszystko jest on lepszy niż aktualizowanie stanu w efekcie. Kiedy wywołujesz funkcję set
podczas renderowania, React wyrenderuje go ponownie tuż po tym, jak zwróci on coś za pomocą instrukcji return
, ale jeszcze przed wyrenderowaniem potomków. Dzięki temu komponenty potomne nie będą renderowały się dwa razy. Pozostała część funkcji komponentu nadal będzie wywołana (a wynik zostanie “wyrzucony do kosza”), dlatego jeśli taki warunek znajduje się pod wywołaniami hooków, możesz dopisać do niego return;
, aby zakończyć renderowanie wcześniej.
Znane problemy
Aktualizuję wartość stanu, ale w konsoli wyświetla mi się stan poprzedni
Wywołanie funkcji set
nie powoduje zmiany stanu w trakcie wykonywania kodu:
function handleClick() {
console.log(count); // 0
setCount(count + 1); // Zażądaj przerenderowania z wartością 1
console.log(count); // Nadal 0!
setTimeout(() => {
console.log(count); // Również 0!
}, 5000);
}
Dzieje się tak dlatego, że stan zachowuje się jak migawka aparatu (ang. snapshot). Aktualizacja stanu wysyła żądanie przerenderowania komponentu z nową wartością, lecz nie wpływa na zmienną javascriptową count
w aktualnie wykoływanym fragmencie kodu.
Jeśli potrzebujesz od razu skorzystać z nowej wartości stanu, przed przekazaniem jej do funkcji set
zapisz ją do zmiennej lokalnej:
const nextCount = count + 1;
setCount(nextCount);
console.log(count); // 0
console.log(nextCount); // 1
Aktualizuję wartość stanu, ale ekran się nie odświeża
React zignoruje aktualizację stanu, jeśli nowa wartość jest identyczna z poprzednim stanem (na podstawie porównania Object.is
). Zwykle przyczyną jest bezpośrednia mutacja obiektu lub tablicy przechowywanych w stanie:
obj.x = 10; // 🚩 Źle: mutacja istniejącego obiektu
setObj(obj); // 🚩 Nic się nie dzieje
Zmutowaliśmy istniejący obiekt obj
, a następnie przekazaliśmy go do setObj
, dlatego React zignorował tę aktualizację. Aby naprawić ten błąd, należy zawsze zastępować obiekty i tablice przechowywane w stanie, zamiast je mutować:
// ✅ Dobrze: tworzymy nowy obiekt
setObj({
...obj,
x: 10
});
Dostaję błąd: “Too many re-renders”
Możesz natknąć się na błąd o treści: Too many re-renders. React limits the number of renders to prevent an infinite loop.
(pol. Zbyt wiele ponownych renderowań. React ogranicza liczbę renderowań, aby zapobiec nieskończonej pętli.). Zwykle oznacza to, że aktualizujemy stan bezwarunkowo podczas renderowania, więc komponent wchodzi w pętlę: renderuje, ustawia stan (co wymusza ponowne wyrenderowanie), renderuje, ustawia stan (co wymusza ponowne wyrenderowanie) itd. Bardzo często przyczyną jest błąd w definicji procedury obsługi zdarzenia:
// 🚩 Źle: wywołuje procedurę obsługi zdarzenia podczas renderowania
return <button onClick={handleClick()}>Kliknij mnie</button>
// ✅ Dobrze: przekazuje procedurę obsługi zdarzenia
return <button onClick={handleClick}>Kliknij mnie</button>
// ✅ Dobrze: przekazuje funkcję "inline"
return <button onClick={(e) => handleClick(e)}>Kliknij mnie</button>
Jeśli nie możesz namierzyć przyczyny tego błędu, kliknij na strzałkę obok treści błędu i przejrzyj stos JavaScriptu w celu znalezienia trefnego wywołania funkcji set
.
Moja funkcja inicjalizująca lub aktualizująca jest uruchamiana dwa razy
W Trybie Restrykcyjnym (ang. Strict Mode) React wywołuje niektóre funkcje dwukrotnie:
function TodoList() {
// Ta funkcja komponentu będzie wywoływana dwukrotnie przy każdym renderowaniu.
const [todos, setTodos] = useState(() => {
// Ta funkcja inicjalizująca zostanie wywołana dwukrotnie podczas tworzenia komponentu.
return createTodos();
});
function handleClick() {
setTodos(prevTodos => {
// Ta funkcja aktualizująca zostanie wywołana dwukrotnie przy każdym kliknięciu.
return [...prevTodos, createTodo()];
});
}
// ...
To zachowanie jest celowe i nie powinno popsuć działania aplikacji.
Takie zachowanie, wystepujące tylko w środowisku deweloperskim, pozwala na sprawdzenie “czystości” komponentów. React wykorzysta wynik z jednego z wywołań tych funkcji, a zignoruje drugi. Dopóki twój komponent oraz funkcje inicjalizujące i aktualizujące są czyste, nic nie powinno się popsuć. Jeśli jednak któraś z nich nie jest czysta, taki mechanizm pomoże ci ją znaleźć i naprawić.
Dla przykładu, poniższa nieczysta funkcja aktualizująca mutuje tablicę przechowywaną w stanie:
setTodos(prevTodos => {
// 🚩 Błąd: mutacja stanu
prevTodos.push(createTodo());
});
Z racji tego, że React wywołuje funkcje aktualizujące dwukrotnie, zauważysz, że zadanie zostanie dodane do listy TODO dwa razy, co będzie wskazywało na błąd. W tym przykładzie możemy to naprawić zastępując tablicę zamiast ją mutować:
setTodos(prevTodos => {
// ✅ Dobrze: zastępujemy nowym stanem
return [...prevTodos, createTodo()];
});
Teraz, kiedy nasza funkcja aktualizująca jest czysta, wywołanie jej dwukrotnie nie spowoduje żadnych różnic w działaniu komponentu. To w taki sposób React pomaga ci znajdować błędy. Tylko komponent oraz funkcje initializujące i aktualizujące muszą być czyste. Procedury obsługi zdarzeń nie muszą być czyste, a React nigdy nie wywoła ich dwukrotnie.
Aby dowiedzieć się więcej, przeczytaj rozdział pt. Czyste komponenty.
Próbuję zapisać w stanie funkcję, ale zamiast tego moja funkcja jest wywoływana
Nie możesz przypisać funkcji do stanu w taki sposób:
const [fn, setFn] = useState(someFunction);
function handleClick() {
setFn(someOtherFunction);
}
Ponieważ przekazujesz funkcję, React zakłada, że someFunction
jest funkcją inicjalizującą i że someOtherFunction
jest funkcją aktualizującą, więc próbuje je wywołać i zapisać wynik ich działania. Aby faktycznie zapisać funkcję, w obydwóch przypadkach musisz poprzedzić je () =>
. Tylko wtedy React zapisze przekazywane przez ciebie funkcje.
const [fn, setFn] = useState(() => someFunction);
function handleClick() {
setFn(() => someOtherFunction);
}