useState: co to jest, hook stanu i składnia w React

Autor: IT Sectr Opublikowano: 2026-07-04 Czas czytania: 9 min

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 — hook do zarządzania stanem lokalnym w komponentach funkcyjnych React.
  • Krotka — zwraca parę [wartość, setter], gdzie setter to funkcja aktualizująca stan.
  • Ponowne renderowanie — wywołanie setter zawsze inicjuje przerysowanie komponentu z nową wartością.
  • Typy danych — useState działa z liczbami, ciągami znaków, tablicami, obiektami i wartościami logicznymi.
  • Niezmienność — stanu nie można mutować bezpośrednio, tylko przez funkcję-setter.

Czym jest useState w React

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.

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

Jak działa useState

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.

Funkcyjna aktualizacja 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.

jsx
// 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)

Składnia useState: deklaracja i aktualizacja

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.

FormaPrzykładKiedy używać
BezpośredniauseState(0)Prosta wartość początkowa
LennauseState(() => compute())Kosztowne początkowe obliczenie
Funkcyjny settersetState(prev => prev + 1)Aktualizacja na podstawie poprzedniej
Bezpośredni settersetState(newValue)Nowa wartość nie zależy od starej
jsx
// Stan obiektu — utwórz nową referencję
const [user, setUser] = useState(() => {
    const saved = localStorage.getItem('user');
    return saved ? JSON.parse(saved) : null;
});

useState z różnymi typami danych

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.

jsx
// ✅ 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]);

Zasady używania hooków z useState

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.

  • Nie w warunkach — if (flag) { useState(0) } narusza kolejność wywoływania hooków.
  • Nie w pętlach — for (...) { useState(0) } czyni kolejność nieprzewidywalną.
  • Nie w callbackach — useEffect(() => { useState(0) }, []) zepsuje listę połączoną.
jsx
// ❌ 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);
    }
}

Typowe błędy z useState

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.

  • Mutacja obiektu — user.name = „Nowe” zamiast setUser({...user, name: „Nowe”}).
  • Nieaktualne domknięcie — użycie nieaktualnej wartości w setter bez funkcyjnej formy.
  • Zbędne przerenderowania — wywołanie setter z tą samą wartością (React 18 i tak wykona renderowanie dla prymitywów).
  • Resetowanie stanu — niezamierzone aktualizowanie klucza key komponentu, co ponownie go montuje.

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.

jsx
// ❌ 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

Czy można używać useState dla złożonych zagnieżdżonych obiektów?

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.

Czy useState jest asynchroniczny czy synchroniczny?

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.

Co się stanie, jeśli wywołam useState poza komponentem?

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.

Kiedy używać useState, a kiedy useReducer?

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.

Dlaczego useState nie aktualizuje wartości od razu?

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

  • useState — podstawowy hook React do zarządzania stanem lokalnym w komponentach funkcyjnych, zwracający [state, setter].
  • Niezmienność — stanu nie można mutować bezpośrednio; zawsze przekazuj nową wartość lub używaj funkcyjnej formy setter.
  • Lenna inicjalizacja — dla kosztownych początkowych obliczeń przekazuj funkcję: useState(() => compute()).
  • Funkcyjny setter — setState(prev => prev + 1) gwarantuje poprawność przy grupowanych aktualizacjach.
  • Grupowanie wsadowe — React grupuje wielokrotne wywołania setter w jedno ponowne renderowanie w celu optymalizacji.
  • Zasady hooków — wywołuj useState tylko na najwyższym poziomie komponentu, nie w warunkach lub pętlach.
  • Typy danych — dla obiektów i tablic zawsze twórz nową referencję przy aktualizacji.

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.

Omów projekt

Przeczytaj również