useState jest hookiem reactowym, który pozwala na dodanie do komponentu zmiennej stanu.

const [state, setState] = useState(initialState);

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.

Zwracana wartość

useState zwraca tablicę o dokładnie dwóch elementach:

  1. Aktualna wartość stanu. Podczas pierwszego renderowania będzie taka sama jak przekazany do hooka argument initialState.
  2. 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.

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 funkcji set, 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ć funkcji flushSync.

  • 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:

  1. Aktualny stan naszej zmiennej stanu, pierwotnie ustawiony na stan początkowy przekazany jako argument.
  2. 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.

Zwróć uwagę

Wywoływanie funkcji set nie zmienia stanu w trakcie działania kodu:

function handleClick() {
setName('Robin');
console.log(name); // Nadal "Taylor"!
}

Wpływa to tylko na to, co useState zwróci przy następnym renderowaniu.

Podstawowe przykłady użycia useState

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:

  1. a => a + 1 otrzyma aktualny stan równy 42 i zwróci następny stan jako 43.
  2. a => a + 1 otrzyma aktualny stan równy 43 i zwróci następny stan jako 44.
  3. a => a + 1 otrzyma aktualny stan równy 44 i zwróci następny stan jako 45.

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

Czy zawsze powinno się używać funkcji aktualizującej?

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

Różnica między użyciem funkcji aktualizującej a przekazaniem nowego stanu bezpośrednio

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łady obiektów i tablic przechowywanych 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”.

Różnica między przekazaniem funkcji inicjalizującej a przekazaniem stanu początkowego bezpośrednio

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ć:

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);
}