useState — to wbudowany hook React, który pozwala komponentom funkcyjnym zarządzać stanem lokalnym bez przekształcania ich w komponenty klasowe. React 16.8 wprowadził hooki, a useState stał się najczęściej używanym z nich, zastępując this.state i this.setState z podejścia klasowego. Według danych React Documentation (2025), ponad 80% komponentów w nowoczesnych aplikacjach React używa useState do zarządzania danymi formularzy, flagami interfejsu i licznikami. Hook zwraca krotkę z bieżącą wartością i funkcją-setter, która aktualizuje stan i inicjuje ponowne renderowanie komponentu.
Najważniejsze
useState — to fundamentalny hook React, dodany w wersji 16.8, który daje komponentom funkcyjnym możliwość przechowywania i zmieniania stanu lokalnego. Przed pojawieniem się hooków stan można było używać tylko w komponentach klasowych przez this.state i this.setState, co czyniło komponenty funkcyjne wyłącznie prezentacyjnymi. useState usunął to ograniczenie, pozwalając pisać całą aplikację na komponentach funkcyjnych.
Hook przyjmuje jeden argument — początkową wartość stanu — i zwraca tablicę z dwóch elementów. Pierwszy element to bieżąca wartość stanu, drugi to funkcja do jego aktualizacji. React gwarantuje, że funkcja-setter jest stabilna i nie zmienia się między renderowaniami, co pozwala bezpiecznie przekazywać ją do komponentów potomnych i używać w domknięciach.
Według danych React DevTools Usage Survey (2024), useState jest używany w 96% aplikacji React, co czyni go najpopularniejszym hookiem w ekosystemie. Nawet w aplikacjach używających globalnego stanu przez Redux lub Zustand, lokalny state przez useState pozostaje głównym mechanizmem zarządzania stanem UI — otwartymi modalami, wartościami pól wejściowych, aktywnymi zakładkami.
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
);
}
Wewnętrzny mechanizm useState opiera się na systemie węzłów fiber React. Każdy komponent w React jest reprezentowany przez węzeł fiber, który przechowuje powiązaną z nim listę hooków w postaci listy połączonej. Gdy komponent wywołuje useState, React tworzy nowy węzeł w tej liście i zapisuje w nim bieżącą wartość stanu oraz referencję do kolejki aktualizacji.
Przy wywołaniu funkcji-setter React nie aktualizuje stanu natychmiast. Zamiast tego umieszcza aktualizację w kolejce (pending state queue), planuje ponowne renderowanie komponentu i dopiero podczas następnego renderowania oblicza nowy stan na podstawie starego i zastosowanych aktualizacji. Zapewnia to grupowanie wsadowe — jeśli w jednym handlerze zdarzenia wywołasz setter trzy razy, React zgrupuje je w jedno ponowne renderowanie.
Według danych React Team — React 18 Working Group (2024), automatyczne grupowanie wsadowe zostało rozszerzone w React 18: teraz aktualizacje stanu są grupowane nie tylko w handlerach zdarzeń, ale także w setTimeout, callbackach Promise i natywnych handlerach. Dało to wzrost wydajności do 30% w scenariuszach z wieloma aktualizacjami stanu.
Gdy nowy stan zależy od poprzedniego, używaj funkcyjnej formy setter: setCount(prev => prev + 1). React przekazuje do funkcji aktualną wartość stanu w momencie zastosowania aktualizacji, co gwarantuje poprawność nawet przy grupowanych aktualizacjach. Bez funkcyjnej formy setCount(count + 1) może używać nieaktualnej wartości, jeśli jest wywołane kilka razy pod rząd.
// Lenna inicjalizacja — uruchamiana tylko raz
setCount(prev => prev + 1);
setCount(prev => prev + 1);
setCount(prev => prev + 1);
// Result: count increased by 3
// NOT batch-safe: stale closure
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
// Result: count increased by 1 (stale closure)
Podstawowa składnia useState jest niezwykle zwięzła: const [state, setState] = useState(initialValue). Wartość początkowa jest używana tylko przy pierwszym renderowaniu; przy kolejnych renderowaniach React ją ignoruje i zwraca bieżącą zapisaną wartość. Jeśli początkowe obliczenie wymaga kosztownych operacji, przekaż funkcję-inicjalizator: useState(() => computeExpensiveInitial()).
Lenna inicjalizacja (lazy initial state) jest szczególnie ważna, gdy wartość początkowa pochodzi z localStorage, złożonego przekształcenia danych lub parsowania parametrów URL. React wywołuje funkcję-inicjalizator tylko raz — podczas montowania komponentu, co oszczędza zasoby przy kolejnych renderowaniach. Bez lennej inicjalizacji kosztowne wyrażenie będzie obliczane przy każdym renderowaniu, nawet jeśli jego wynik jest ignorowany.
| Forma | Przykład | Kiedy używać |
|---|---|---|
| Bezpośrednia | useState(0) | Prosta wartość początkowa |
| Lenna | useState(() => compute()) | Kosztowne początkowe obliczenie |
| Funkcyjny setter | setState(prev => prev + 1) | Aktualizacja na podstawie poprzedniej |
| Bezpośredni setter | setState(newValue) | Nowa wartość nie zależy od starej |
// Stan obiektu — utwórz nową referencję
const [user, setUser] = useState(() => {
const saved = localStorage.getItem('user');
return saved ? JSON.parse(saved) : null;
});
useState działa równie dobrze z prymitywami (liczby, ciągi znaków, wartości logiczne) i typami referencyjnymi (tablice, obiekty). Jest jednak ważna różnica: dla prymitywów React określa zmianę po wartości, dla obiektów i tablic — po referencji. Mutacja istniejącego obiektu bez tworzenia nowego nie spowoduje ponownego renderowania, ponieważ referencja pozostała ta sama.
Podczas pracy z obiektami i tablicami zawsze twórz nową kopię ze zmienionymi danymi. Dla obiektów używaj składni spread: setUser(prev => ({...prev, name: newName})). Dla tablic — metody zwracające nową tablicę: filter, map, concat, lub składnia spread do dodawania elementów. Metody mutacji takie jak push, pop, splice bezpośrednio nie zadziałają — React je zignoruje, ponieważ referencja do tablicy się nie zmieniła.
// ✅ Prawidłowo: hooki na najwyższym poziomie
const [form, setForm] = useState({ name: '', email: '' });
const updateField = (field, value) =>
setForm(prev => ({ ...prev, [field]: value }));
// ...
const [items, setItems] = useState([]);
const addItem = item =>
setItems(prev => [item, ...prev]);
Jak wszystkie hooki React, useState podlega dwóm zasadom. Po pierwsze: wywołuj hooki tylko na najwyższym poziomie komponentu — nie można umieszczać ich wewnątrz warunków, pętli lub zagnieżdżonych funkcji. Gwarantuje to, że hooki są wywoływane w tej samej kolejności przy każdym renderowaniu, co jest krytyczne dla wewnętrznej listy połączonej React.
Druga zasada: wywołuj hooki tylko z komponentów funkcyjnych React lub własnych hooków. Nie wywołuj useState z zwykłych funkcji, callbacków lub komponentów klasowych. Naruszenie tych zasad prowadzi do błędu Invalid hook call, który React wyłapuje na etapie wykonania.
// ❌ Problem nieaktualnego domknięcia
function GoodComponent() {
const [count, setCount] = useState(0);
const [name, setName] = useState('');
// ✅ Naprawione przez aktualizację funkcyjną
}
// licznik jest nieaktualny!
function BadComponent() {
if (isLogged) {
const [user, setUser] = useState(null);
}
}
Najczęstszym błędem jest bezpośrednia mutacja obiektów i tablic zamiast tworzenia nowych referencji. Programiści przyzwyczaili się do mutacyjnego stylu z komponentów klasowych, gdzie this.state.user.name = „Nowa nazwa” działało (choć też nie było zalecane). W komponentach funkcyjnych taka mutacja jest po prostu ignorowana: React nie widzi zmiany referencji i nie przerysowuje komponentu.
Problem nieaktualnego domknięcia (stale closure) występuje, gdy callback przekazany do useEffect lub handlera zdarzenia „chwyta” nieaktualną wartość stanu z domknięcia w momencie tworzenia callbacka. Rozwiązaniem jest użycie funkcyjnej formy settera lub dołączenie potrzebnych wartości do tablicy zależności useEffect. Według danych React Documentation — Hooks FAQ (2025), nieaktualne domknięcia są drugą najczęstszą przyczyną błędów w hookach po nieprawidłowej tablicy zależności.
// ❌ Stale closure problem
useEffect(() => {
const timer = setInterval(() => {
setCount(count + 1); // count is stale!
}, 1000);
return () => clearInterval(timer);
}, []);
// ✅ Fixed with functional update
useEffect(() => {
const timer = setInterval(() => {
setCount(prev => prev + 1);
}, 1000);
return () => clearInterval(timer);
}, []);
Często zadawane pytania
Tak, ale z pewną ostrożnością. Dla głęboko zagnieżdżonych obiektów każdy poziom wymaga kopiowania przy aktualizacji: setState(prev => ({...prev, nested: {...prev.nested, key: newValue}})). Jeśli masz obiekty z trzema lub więcej poziomami zagnieżdżenia, rozważ Immer (biblioteka do niezmiennej aktualizacji) lub podziel stan na kilka useState dla różnych poziomów.
useState jest asynchroniczny w tym sensie, że wywołanie setter nie aktualizuje wartości natychmiast. React umieszcza aktualizację w kolejce i wykonuje ją podczas następnego renderowania. Po setCount(newValue) zmienna count w bieżącej funkcji nadal zawiera starą wartość. Nowa wartość będzie dostępna dopiero przy następnym wywołaniu komponentu. Mechanizm grupowania wsadowego gwarantuje, że wielokrotne wywołania są zgrupowane w jedno renderowanie.
React wyrzuci błąd Invalid hook call. Hooki mogą być wywoływane tylko wewnątrz komponentów funkcyjnych React lub własnych hooków (funkcji, których nazwa zaczyna się od use). To ograniczenie jest wbudowane w implementację hooków: używają one drzewa fiber komponentu do przechowywania stanu, a poza komponentem to drzewo jest niedostępne.
useState nadaje się do prostych niezależnych stanów (liczniki, pola wejściowe, flagi). useReducer jest lepszy, gdy stan jest złożonym obiektem z wieloma polami, logika aktualizacji zależy od typu akcji lub następny stan wymaga złożonej transformacji. Praktyczna zasada: jeśli masz więcej niż trzy powiązane ze sobą pola stanu — użyj useReducer.
Jest to zrobione dla wydajności. Gdyby useState aktualizował stan synchronicznie, każde wywołanie settera powodowałoby natychmiastowe ponowne renderowanie, co prowadziłoby do wielokrotnych przerysowań wewnątrz jednego handlera zdarzenia. Grupowanie wsadowe, w którym React grupuje aktualizacje i wykonuje jedno ponowne renderowanie, to optymalizacja wprowadzona już w React 16 i znacznie ulepszona w React 18 z automatycznym grupowaniem wsadowym.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również