Hooki React to funkcje, takie jak useState, useEffect i useRef, które dają komponentom funkcyjnym stan, efekty uboczne i refy. Ta ściąga omawia każdy wbudowany hook w React 19.3 z jednozdaniowym opisem celu, przykładem na żywo i najczęstszym błędem, a do tego 12 własnych hooków do skopiowania.
W chwili pisania (październik 2026) najnowsze wydanie React to 19.3. Tabela wymienia każdy hook z oficjalnej dokumentacji hooków React oraz trzy hooki React 19 opisane na osobnych stronach (use, useOptimistic i hook React DOM useFormStatus). Hooki oznaczone React 18 działają w React 18 i 19. Hooków oznaczonych React 19 nie ma w React 18. Każdy hook z React 18 poniżej ma przykład na żywo, który możesz kliknąć; hooki React 19 są pokazane tylko jako kod, ponieważ ta strona działa na React 18.
| Hook | Grupa | Do czego służy | Od wersji |
|---|---|---|---|
| useState | Stan | przechowuje wartość, której zmiana ponownie renderuje komponent | React 18 |
| useReducer | Stan | zarządza stanem z wieloma powiązanymi aktualizacjami przez jedną funkcję reducera | React 18 |
| useContext | Kontekst | odczytuje wartość z najbliższego providera powyżej | React 18 |
| useRef | Ref | przechowuje zmienną wartość lub węzeł DOM, który nie wywołuje renderów | React 18 |
| useImperativeHandle | Ref | wybiera, które metody rodzic może wywołać przez ref | React 18 |
| useEffect | Efekty | synchronizuje z czymś poza React po namalowaniu | React 18 |
| useLayoutEffect | Efekty | mierzy układ, zanim przeglądarka namaluje stronę | React 18 |
| useInsertionEffect | Efekty | wstawia reguły stylów przed efektami układu (biblioteki CSS-in-JS) | React 18 |
| useEffectEvent | Efekty | odczytuje najnowsze propsy wewnątrz efektu bez jego ponownego uruchamiania | React 19.2 |
| useMemo | Wydajność | zapamiętuje kosztowne obliczenie między renderami | React 18 |
| useCallback | Wydajność | zapamiętuje funkcję, aby zmemoizowane dzieci nie renderowały się ponownie | React 18 |
| useTransition | Wydajność | oznacza aktualizację stanu jako niepilną i daje flagę oczekiwania | React 18 |
| useDeferredValue | Wydajność | pozwala wolnej części UI zostawać w tyle za szybkim pisaniem | React 18 |
| useId | Inne | generuje stabilne, unikalne ID dla etykiet i atrybutów ARIA | React 18 |
| useSyncExternalStore | Inne | subskrybuje magazyn danych działający poza React | React 18 |
| useDebugValue | Inne | nadaje etykietę własnemu hookowi w React DevTools | React 18 |
| useActionState | Inne | śledzi wynik i stan oczekiwania akcji formularza | React 19 |
| useOptimistic | Inne | pokazuje oczekiwany wynik akcji, zanim się zakończy | React 19 |
| use | API | odczytuje promise lub kontekst, nawet wewnątrz warunków | React 19 |
| useFormStatus | React DOM | odczytuje stan oczekiwania formularza nadrzędnego | React 19 |
Hooki podlegają dwóm zasadom: wywołuj je tylko na najwyższym poziomie komponentu lub własnego hooka (nigdy w pętlach, warunkach ani funkcjach zagnieżdżonych) i wywołuj je tylko z funkcyjnych komponentów React lub innych hooków. use to jedyny wyjątek od pierwszej zasady: można go wywołać wewnątrz bloków if i pętli. Pakiet eslint-plugin-react-hooks sprawdza obie zasady za Ciebie.
useStateCo robi: Przechowuje wartość dla tego komponentu. Wywołanie settera planuje ponowny render z nową wartością.
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return (
<>
<button onClick={() => setCount((c) => c - 1)}>-1</button>
<span>{count}</span>
<button onClick={() => setCount((c) => c + 1)}>+1</button>
</>
);
}
Częsty błąd: Wpisanie setCount(count + 1) trzy razy w jednym kliknięciu i oczekiwanie +3. Wszystkie trzy odczytują to samo count z tego renderu, więc dostajesz +1. Gdy następna wartość zależy od poprzedniej, przekaż funkcję aktualizującą, setCount(c => c + 1). Przycisk "+3 (updater)" powyżej robi dokładnie to. Drugi klasyk: modyfikowanie obiektu lub tablicy w miejscu (user.name = "x"; setUser(user)). React porównuje referencje, widzi ten sam obiekt i pomija render. Zamiast tego utwórz nowy obiekt: setUser({ ...user, name: "x" }).
useReducerCo robi: Przenosi logikę aktualizacji do jednej czystej funkcji, która przyjmuje bieżący stan i akcję, a zwraca następny stan. Przydatne, gdy kilka wartości zmienia się razem.
type Action = { type: "add"; text: string } | { type: "toggle"; id: number };
function reducer(state: Todo[], action: Action): Todo[] {
switch (action.type) {
case "add":
return [...state, { id: Date.now(), text: action.text, done: false }];
case "toggle":
return state.map((t) => (t.id === action.id ? { ...t, done: !t.done } : t));
}
}
const [todos, dispatch] = useReducer(reducer, []);
dispatch({ type: "add", text: "Ship it" });
Częsty błąd: Wykonywanie efektów ubocznych wewnątrz reducera (pobieranie danych, zapis do localStorage, generowanie losowych ID z timera, na którym później polegasz). W trybie deweloperskim Strict Mode React wywołuje reducery dwa razy, aby ujawnić nieczysty kod. Utrzymuj reducer czystym, a efekty uboczne umieszczaj w obsłudze zdarzeń lub efektach.
useContextCo robi: Odczytuje wartość najbliższego pasującego providera powyżej tego komponentu i renderuje ponownie, gdy ta wartość się zmieni.
const ThemeContext = createContext<"light" | "dark">("light");
function App() {
const [theme, setTheme] = useState<"light" | "dark">("light");
return (
<ThemeContext.Provider value={theme}>
<Toolbar />
</ThemeContext.Provider>
);
}
function Badge() {
const theme = useContext(ThemeContext);
return <span className={theme}>Current theme: {theme}</span>;
}
Częsty błąd: Przekazywanie nowego literału obiektu jako wartości providera przy każdym renderze, na przykład value={{ user, setUser }}. Każdy render tworzy nowy obiekt, więc każdy konsument renderuje się ponownie, nawet gdy nic się nie zmieniło. Owiń wartość w useMemo albo rozdziel rzadko i często zmieniające się wartości na dwa konteksty. Sprawdź też, czy provider znajduje się powyżej komponentu: wywołanie useContext w tym samym komponencie, który renderuje providera, zwraca wartość domyślną.
useRefCo robi: Daje Ci pudełko (ref.current), które przetrwa ponowne rendery. Jego zmiana nie powoduje renderu. Używaj go do węzłów DOM, ID timerów i innych wartości, których ekran nie pokazuje.
const inputRef = useRef<HTMLInputElement>(null);
const clicks = useRef(0);
<input ref={inputRef} />
<button onClick={() => inputRef.current?.focus()}>Focus</button>
<button onClick={() => { clicks.current += 1; }}>Count silently</button>
Częsty błąd: Wyświetlanie ref.current w JSX i oczekiwanie, że się zaktualizuje. Refy nie wywołują renderów, więc ekran nadrabia zaległości dopiero, gdy coś innego się przerenderuje (wypróbuj przycisk "Dodaj do refa", a potem "Przerenderuj" powyżej). Jeśli wartość ma się pojawić na ekranie, jej miejsce jest w stanie. Unikaj też odczytu i zapisu refów podczas renderu, poza leniwą jednorazową inicjalizacją.
useImperativeHandleCo robi: Dostosowuje to, co otrzymuje rodzic, gdy przypina ref do Twojego komponentu, dzięki czemu udostępniasz kilka nazwanych metod zamiast całego węzła DOM.
type Handle = { shake: () => void; clear: () => void };
// React 18: wrap in forwardRef. In React 19 you can accept ref as a normal prop.
const FancyInput = forwardRef<Handle, Props>(function FancyInput(props, ref) {
const el = useRef<HTMLInputElement>(null);
useImperativeHandle(ref, () => ({
shake() { /* animate */ el.current?.focus(); },
clear() { if (el.current) el.current.value = ""; },
}), []);
return <input ref={el} />;
});
// parent
const ref = useRef<Handle>(null);
<FancyInput ref={ref} />
<button onClick={() => ref.current?.shake()}>Shake</button>
Częsty błąd: Używanie go do sterowania rzeczami, które można wyrazić propsami. Jeśli rodzic wywołuje ref.current.open(), aby pokazać okno modalne, prop isOpen jest zwykle prostszy i łatwiejszy do śledzenia. Zostaw imperatywne uchwyty dla fokusu, przewijania, odtwarzania i pauzy oraz animacji. Pominięcie tablicy zależności (trzeci argument) sprawia też, że uchwyt jest tworzony od nowa przy każdym renderze.
useEffectCo robi: Uruchamia kod po tym, jak React zaktualizuje ekran, aby utrzymać komponent w synchronizacji z czymś poza React: timerem, subskrypcją, żądaniem sieciowym, widżetem spoza React. Zwróć funkcję czyszczącą, aby to cofnąć.
useEffect(() => {
if (!running) return;
const id = setInterval(() => setSeconds((s) => s + 1), 1000);
return () => clearInterval(id); // runs before the next effect and on unmount
}, [running]);
Częsty błąd: Używanie efektu do obliczenia czegoś, co można policzyć podczas renderu, na przykład useEffect(() => setFullName(first + " " + last), [first, last]). To kosztuje dodatkowy render i może przez jedną klatkę pokazywać nieaktualne wartości. Zamiast tego napisz const fullName = first + " " + last. Drugi najczęstszy błąd to brak funkcji czyszczącej: w trybie deweloperskim Strict Mode React montuje, odmontowuje i ponownie montuje każdy komponent raz, więc efekt bez czyszczenia zasubskrybuje lub uruchomi timer dwa razy. To podwójne uruchomienie jest wskazówką, a nie błędem w React.
useLayoutEffectCo robi: Ma tę samą postać co useEffect, ale uruchamia się po zmianie DOM przez React, a przed namalowaniem strony przez przeglądarkę. Używaj go do zmierzenia elementu i dostosowania układu, aby użytkownicy nigdy nie zobaczyli złej pozycji.
const boxRef = useRef<HTMLSpanElement>(null);
const [width, setWidth] = useState(0);
useLayoutEffect(() => {
setWidth(boxRef.current!.getBoundingClientRect().width);
}, [text]);
Częsty błąd: Sięganie po niego domyślnie. Blokuje malowanie, więc powolna praca w jego wnętrzu sprawia, że strona wydaje się zawieszona. Używaj useEffect, chyba że widzisz migotanie, które naprawia tylko pomiar przed malowaniem (podpowiedzi, pozycjonowanie dymków, automatyczne dopasowanie rozmiaru). Nie robi też nic podczas renderowania na serwerze, co w niektórych konfiguracjach generuje ostrzeżenie.
useInsertionEffectCo robi: Uruchamia się przed wszystkimi efektami układu, aby biblioteka mogła wstawić reguły <style>, zanim cokolwiek odczyta układ. Istnieje dla autorów bibliotek CSS-in-JS.
function useScopedRule(selector: string, rule: string) {
useInsertionEffect(() => {
const style = document.createElement("style");
style.textContent = `${selector} { ${rule} }`;
document.head.appendChild(style);
return () => style.remove();
}, [selector, rule]);
}
Częsty błąd: Używanie go w kodzie aplikacji albo próba aktualizowania z niego stanu. Nie możesz w nim planować aktualizacji, a refy nie są jeszcze podpięte. Jeśli nie piszesz biblioteki do stylowania, prawie na pewno potrzebujesz useEffect lub useLayoutEffect.
useEffectEventCo robi: React 19.2 i nowsze. Owija funkcję tak, aby efekt mógł ją wywołać i zawsze widzieć najnowsze propsy i stan, bez wymieniania tych wartości jako zależności. Używaj go do części "zdarzeniowej" efektu, na przykład logowania lub pokazania powiadomienia z bieżącym motywem.
// React 19.2+ only. Not run on this page.
import { useEffect, useEffectEvent } from "react";
function ChatRoom({ roomId, theme }) {
const onConnected = useEffectEvent(() => {
showNotification("Connected", theme); // always the latest theme
});
useEffect(() => {
const conn = createConnection(roomId);
conn.on("connected", () => onConnected());
conn.connect();
return () => conn.disconnect();
}, [roomId]); // theme is not a dependency, and onConnected must not be listed
}
Częsty błąd: Umieszczanie zdarzenia efektu w tablicy zależności, wywoływanie go podczas renderu lub przekazywanie go innym komponentom. Dokumentacja React mówi, aby wywoływać zdarzenia efektów tylko z wnętrza efektów (lub innych zdarzeń efektów), a jej reguła lintera ostrzega, gdy wymienisz je jako zależność. W React 18 typowym zamiennikiem jest wzorzec "najnowszego refa" używany przez useInterval niżej na tej stronie.
useMemoCo robi: Zapamiętuje wynik obliczenia i przelicza go tylko wtedy, gdy zmienią się jego zależności.
const filtered = useMemo(
() => items.filter((item) => item.includes(query)),
[items, query],
);
Częsty błąd: Owijanie w niego wszystkiego. Memoizacja taniej pracy, takiej jak a + b czy krótkie map, dodaje kod i koszt porównań bez żadnego zysku. Najpierw zmierz (React DevTools Profiler) i memoizuj wolne obliczenia, wartości przekazywane do dzieci w memo oraz wartości używane jako zależności efektów. Pokrewna pułapka: zależność, która przy każdym renderze jest nowym obiektem ({ sort: "asc" }), sprawia, że memo przelicza się za każdym razem.
useCallbackCo robi: Zwraca tę samą instancję funkcji między renderami, dopóki nie zmieni się zależność. Ma to znaczenie, gdy funkcja jest przekazywana do dziecka owiniętego w memo albo używana jako zależność efektu.
const handleAdd = useCallback(() => setCount((c) => c + 1), []);
const Child = memo(function Child({ onClick }: { onClick: () => void }) {
return <button onClick={onClick}>Add</button>;
});
<Child onClick={handleAdd} />
Częsty błąd: Używanie useCallback, gdy dziecko nie jest zmemoizowane. Bez memo na dziecku i tak renderuje się ono razem z rodzicem, więc stabilna funkcja niczego nie zmienia. Demo na żywo pokazuje oboje dzieci: tylko to, które otrzymuje zapamiętaną funkcję, pomija rendery. Drugą pułapką są nieaktualne domknięcia: jeśli callback odczytuje count, ale lista zależności jest pusta, wciąż widzi pierwszą wartość. Użyj funkcji aktualizującej lub wymień zależność.
useTransitionCo robi: Oznacza aktualizację stanu jako niepilną. React utrzymuje responsywność bieżącego ekranu, przygotowując nowy, i daje Ci flagę isPending do wskaźnika ładowania.
const [isPending, startTransition] = useTransition();
function selectTab(next: string) {
startTransition(() => {
setTab(next);
});
}
Częsty błąd: Owijanie w przejście własnego stanu pola tekstowego. Kontrolowane pola muszą aktualizować się pilnie, inaczej pisanie wydaje się zepsute. Zostaw stan pola zwykłym, a wolną, pochodną aktualizację przenieś do przejścia (lub użyj useDeferredValue). W React 18 funkcja przekazana do startTransition musi być synchroniczna; React 19 przyjmuje też funkcje asynchroniczne.
useDeferredValueCo robi: Daje Ci kopię wartości, która może zostawać w tyle. Szybkie części UI używają prawdziwej wartości; wolna część renderuje się z odroczoną i nadrabia, gdy przeglądarka jest bezczynna.
const [query, setQuery] = useState("");
const deferredQuery = useDeferredValue(query);
const isStale = query !== deferredQuery;
<input value={query} onChange={(e) => setQuery(e.target.value)} />
<div style={{ opacity: isStale ? 0.5 : 1 }}>
<SlowResults query={deferredQuery} />
</div>
Częsty błąd: Zapominanie o owinięciu wolnego dziecka w memo. Jeśli dziecko i tak renderuje się z rodzicem przy każdym naciśnięciu klawisza, odroczenie jego propa nie pomaga. To też nie jest debounce: nie czeka określonego czasu i nie zmniejsza liczby żądań sieciowych. Aby wysyłać mniej żądań, użyj debounce (zobacz useDebounce poniżej).
useIdCo robi: Generuje ID unikalne w obrębie aplikacji i identyczne na serwerze i kliencie, dla htmlFor, aria-describedby i podobnych atrybutów.
function Field({ label }: { label: string }) {
const id = useId();
return (
<>
<label htmlFor={id}>{label}</label>
<input id={id} />
</>
);
}
Częsty błąd: Używanie go do tworzenia kluczy list. Klucze powinny pochodzić z Twoich danych (ID z bazy danych), a nie z hooka. Unikaj też Math.random() do ID elementów w aplikacjach renderowanych na serwerze: serwer i przeglądarka generują różne liczby i hydracja się nie udaje, a to właśnie problem, który rozwiązuje useId.
useSyncExternalStoreCo robi: Subskrybuje komponent do źródła danych, którym React nie zarządza (API przeglądarki, globalny magazyn), i odczytuje migawkę, która pozostaje spójna podczas renderowania współbieżnego.
function subscribe(callback: () => void) {
window.addEventListener("online", callback);
window.addEventListener("offline", callback);
return () => {
window.removeEventListener("online", callback);
window.removeEventListener("offline", callback);
};
}
const online = useSyncExternalStore(
subscribe,
() => navigator.onLine, // client snapshot
() => true, // server snapshot
);
Częsty błąd: Definiowanie subscribe bezpośrednio wewnątrz komponentu. Nowa funkcja przy każdym renderze sprawia, że React za każdym razem anuluje i ponawia subskrypcję. Zdefiniuj ją poza komponentem lub owiń w useCallback. Funkcja migawki musi też zwracać tę samą wartość, gdy nic się nie zmieniło: zwracanie nowego obiektu lub tablicy przy każdym wywołaniu powoduje nieskończoną pętlę renderów.
useDebugValueCo robi: Dodaje etykietę do własnego hooka w React DevTools. Nie ma wpływu na to, co widzą użytkownicy.
function useOnlineStatus() {
const online = useSyncExternalStore(subscribe, () => navigator.onLine);
useDebugValue(online ? "Online" : "Offline");
return online;
}
Częsty błąd: Dodawanie go do każdego własnego hooka lub wykonywanie w nim kosztownego formatowania. Używaj go w współdzielonych hookach, których stan trudno odczytać w DevTools, a jeśli etykieta jest kosztowna do zbudowania, przekaż formater jako drugi argument: useDebugValue(date, d => d.toISOString()).
use, useActionState, useOptimistic, useFormStatus) nie są eksportowane przez React 18. Sama ta strona działa na React 18, więc są pokazane jako kod, który możesz skopiować do projektu w React 19, bez dem na żywo. useEffectEvent, opisany wyżej, wymaga React 19.2.useActionStateCo robi: Owija akcję formularza i daje Ci jej najnowszy wynik oraz flagę oczekiwania. Akcja otrzymuje poprzedni stan i wysłane FormData.
// React 19+
import { useActionState } from "react";
async function subscribe(prev: { error?: string; ok?: boolean }, formData: FormData) {
const email = String(formData.get("email") ?? "");
if (!email.includes("@")) return { error: "Enter a valid email" };
await saveEmail(email);
return { ok: true };
}
function Newsletter() {
const [state, formAction, isPending] = useActionState(subscribe, {});
return (
<form action={formAction}>
<input name="email" type="email" />
<button disabled={isPending}>{isPending ? "Saving..." : "Subscribe"}</button>
{state.error && <p role="alert">{state.error}</p>}
{state.ok && <p>Thanks, you are subscribed.</p>}
</form>
);
}
Częsty błąd: Zapominanie, że pierwszym argumentem akcji jest poprzedni stan. Napisanie async function subscribe(formData) oznacza, że formData to w rzeczywistości stary stan, a .get rzuca wyjątek. Pamiętaj też, że w okresie wersji canary React 19 hook przez krótki czas nazywał się useFormState (w react-dom); aktualna dokumentacja używa useActionState z react.
useOptimisticCo robi: Pokazuje tymczasową, optymistyczną wersję stanu, gdy działa akcja asynchroniczna, a po jej zakończeniu wraca do prawdziwego stanu.
// React 19+
import { useOptimistic } from "react";
function Thread({ messages, send }: Props) {
const [optimistic, addOptimistic] = useOptimistic(
messages,
(current, text: string) => [...current, { text, sending: true }],
);
async function action(formData: FormData) {
const text = String(formData.get("text"));
addOptimistic(text);
await send(text); // parent updates `messages` when this resolves
}
return (
<form action={action}>
{optimistic.map((m, i) => <p key={i}>{m.text}{m.sending && " (sending)"}</p>)}
<input name="text" />
</form>
);
}
Częsty błąd: Wywoływanie optymistycznego settera poza akcją lub przejściem. React oczekuje go wewnątrz akcji formularza lub startTransition; gdzie indziej optymistyczna wartość pojawia się i natychmiast znika (React ostrzega w trybie deweloperskim). Zawsze też obsługuj niepowodzenie: jeśli send rzuci wyjątek, pokaż błąd, aby wiadomość nie zniknęła po cichu.
useCo robi: Odczytuje wartość promise (wstrzymując render do jego rozwiązania) lub kontekstu. W przeciwieństwie do innych hooków można go wywołać wewnątrz warunków i pętli.
// React 19+
import { use, Suspense } from "react";
function Comments({ commentsPromise }: { commentsPromise: Promise<Comment[]> }) {
const comments = use(commentsPromise); // suspends until resolved
return comments.map((c) => <p key={c.id}>{c.text}</p>);
}
function Page({ commentsPromise }) {
return (
<Suspense fallback={<p>Loading comments...</p>}>
<Comments commentsPromise={commentsPromise} />
</Suspense>
);
}
// Conditional context read, which useContext cannot do:
function Heading({ show }: { show: boolean }) {
if (!show) return null;
const theme = use(ThemeContext);
return <h1 className={theme}>Hello</h1>;
}
Częsty błąd: Tworzenie promise wewnątrz komponentu, który wywołuje use, na przykład use(fetch(url)). Każdy render tworzy nowy promise, więc komponent pozostaje wstrzymany na zawsze. Utwórz promise raz (w komponencie serwerowym, loaderze, cache lub rodzicu) i przekaż go w dół. use nie może też znajdować się w try/catch; obsługuj błędy przez error boundary.
useFormStatusCo robi: Importowany z react-dom. Odczytuje status nadrzędnego <form>: pending, data, method i action.
// React 19+
import { useFormStatus } from "react-dom";
function SubmitButton() {
const { pending } = useFormStatus();
return <button type="submit" disabled={pending}>{pending ? "Sending..." : "Send"}</button>;
}
function ContactForm() {
return (
<form action={sendMessage}>
<textarea name="message" />
<SubmitButton />
</form>
);
}
Częsty błąd: Wywoływanie go w tym samym komponencie, który renderuje <form>. Raportuje tylko o formularzu powyżej wywołującego komponentu, więc w komponencie samego formularza pending zawsze ma wartość false. Przenieś przycisk do osobnego komponentu, jak powyżej.
useEffect, useLayoutEffect, useMemo, useCallback and useImperativeHandle przyjmują listę wartości jako ostatni argument. React porównuje każdy element z wartością z poprzedniego renderu za pomocą Object.is. Jeśli którykolwiek element się zmienił, efekt uruchamia się ponownie (lub memo przelicza się od nowa). Ta jedna zasada wyjaśnia każdy przypadek:
| Piszesz | Efekt uruchamia się | Typowe użycie |
|---|---|---|
| useEffect(fn) | po każdym renderze | rzadkie; zwykle znak, że coś powinno być obliczane podczas renderu |
| useEffect(fn, []) | raz po zamontowaniu (dwa razy w trybie deweloperskim Strict Mode), czyszczenie przy odmontowaniu | subskrypcje i konfiguracja, które nie używają propsów ani stanu |
| useEffect(fn, [a, b]) | po zamontowaniu, a potem przy każdej zmianie a lub b | pobieranie po ID, synchronizacja z propem |
Kliknij przyciski: zmiana b ponownie uruchamia tylko efekt bez tablicy, zmiana a uruchamia zarówno ten, jak i efekt [a], a efekt [] po zamontowaniu milczy.
eslint-plugin-react-hooks i pozwól, by exhaustive-deps powiedział Ci, czego brakuje.{ id } czy () => {} nigdy nie jest równe poprzedniemu według Object.is, więc ponownie uruchamia efekt przy każdym renderze. Przenieś je do wnętrza efektu, poza komponent albo je zmemoizuj.[user.id] jest stabilniejsze niż [user], gdy liczy się tylko ID.setCount(c => c + 1) nie odczytuje count, więc nie trzeba wymieniać count.useEffectEvent w React 19.2+ albo refa, który aktualizujesz na bieżąco w React 18.// Stale closure: logs 0 forever
useEffect(() => {
const id = setInterval(() => console.log(count), 1000);
return () => clearInterval(id);
}, []); // count is missing
// Fixed: re-subscribe when count changes
useEffect(() => {
const id = setInterval(() => console.log(count), 1000);
return () => clearInterval(id);
}, [count]);
// Fetch with cleanup so a slow old response cannot overwrite a newer one
useEffect(() => {
const controller = new AbortController();
fetch(`/api/users/${userId}`, { signal: controller.signal })
.then((r) => r.json())
.then(setUser)
.catch((err) => { if (err.name !== "AbortError") setError(err); });
return () => controller.abort();
}, [userId]);
Własny hook to funkcja, której nazwa zaczyna się od use i która wywołuje inne hooki. Pozwala kilku komponentom współdzielić logikę ze stanem; każdy komponent, który go wywołuje, dostaje własny, niezależny stan. Poniższe hooki zostały napisane dla tej strony w TypeScript, działają w React 18 i 19 i nie mają zależności. Każdy kończy się uwagą, kiedy go nie używać.
useDebounceZwraca wartość, która aktualizuje się dopiero, gdy wejście przestanie się zmieniać przez delay milisekund. Dobre dla pól wyszukiwania wywołujących API.
import { useEffect, useState } from "react";
export function useDebounce<T>(value: T, delay = 300): T {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const id = window.setTimeout(() => setDebounced(value), delay);
return () => window.clearTimeout(id);
}, [value, delay]);
return debounced;
}
// const debouncedQuery = useDebounce(query, 400);
// useEffect(() => { search(debouncedQuery); }, [debouncedQuery]);
Kiedy go nie używać: Gdy potrzebujesz tylko, aby UI pozostało responsywne podczas renderowania dużej listy. To problem renderowania, a useDeferredValue rozwiązuje go bez sztucznego opóźnienia. Pomiń go też przy walidacji formularzy, która powinna działać natychmiast.
useLocalStorageDziała jak useState, ale zapisuje wartość w localStorage i przywraca ją przy następnej wizycie.
import { useEffect, useState } from "react";
export function useLocalStorage<T>(key: string, initial: T) {
const [value, setValue] = useState<T>(() => {
try {
const raw = window.localStorage.getItem(key);
return raw === null ? initial : (JSON.parse(raw) as T);
} catch {
return initial; // private mode, blocked storage or bad JSON
}
});
useEffect(() => {
try {
window.localStorage.setItem(key, JSON.stringify(value));
} catch {
/* storage full or blocked: keep the in-memory value */
}
}, [key, value]);
return [value, setValue] as const;
}
Kiedy go nie używać: Do niczego wrażliwego (tokeny, dane osobowe), ponieważ każdy skrypt na stronie może odczytać localStorage. Także nie bez ostrożności na stronach renderowanych na serwerze: serwer nie ma window, więc odczytuj magazyn po zamontowaniu, aby uniknąć niezgodności hydracji. Nie synchronizuje się między kartami; jeśli tego potrzebujesz, dodaj nasłuchiwanie zdarzenia storage.
useMediaQueryZwraca true, dopóki media query CSS jest spełnione, i aktualizuje się, gdy to się zmieni.
import { useCallback, useSyncExternalStore } from "react";
export function useMediaQuery(query: string): boolean {
const subscribe = useCallback((onChange: () => void) => {
const mql = window.matchMedia(query);
mql.addEventListener("change", onChange);
return () => mql.removeEventListener("change", onChange);
}, [query]);
return useSyncExternalStore(
subscribe,
() => window.matchMedia(query).matches,
() => false, // server snapshot
);
}
// const isDesktop = useMediaQuery("(min-width: 1024px)");
// const reduceMotion = useMediaQuery("(prefers-reduced-motion: reduce)");
Kiedy go nie używać: Do zmian układu, które CSS potrafi wykonać sam. Ukrywanie lub przestawianie elementów breakpointami Tailwind (md:hidden) działa, zanim załaduje się JavaScript, i nigdy nie miga. Używaj hooka, gdy musisz renderować różne komponenty lub całkowicie pominąć ładowanie czegoś.
useToggleWartość logiczna ze stabilną funkcją toggle, do menu, okien modalnych i rozwijanych sekcji.
import { useCallback, useState } from "react";
export function useToggle(initial = false) {
const [on, setOn] = useState(initial);
const toggle = useCallback(() => setOn((v) => !v), []);
return [on, toggle, setOn] as const;
}
// const [open, toggleOpen, setOpen] = useToggle();
Kiedy go nie używać: Gdy stan ma więcej niż dwie wartości (zamknięty, otwierający się, otwarty, błąd). Zamiast mnożyć przełączniki, użyj unii stringów z useState lub useReducer.
usePreviousZwraca wartość z poprzedniego renderu, przydatną do pokazania zmiany ("wzrost z 12") lub reagowania na przejście.
import { useEffect, useRef } from "react";
export function usePrevious<T>(value: T): T | undefined {
const ref = useRef<T>();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
Kiedy go nie używać: Do decydowania, czy zaktualizować stan. Dokumentacja React pokazuje do tego czystszy wzorzec: przechowuj poprzednią wartość w stanie i porównuj podczas renderu. Ten hook odczytuje ref podczas renderu, co dziś działa, ale to rodzaj kodu, który według wskazówek React powinien być rzadki.
useOnClickOutsideWywołuje funkcję obsługi, gdy użytkownik naciśnie gdziekolwiek poza danym elementem. Typowe przy zamykaniu własnego menu lub dymka.
import { useEffect, useRef, type RefObject } from "react";
export function useOnClickOutside(
ref: RefObject<HTMLElement>,
handler: (event: PointerEvent) => void,
) {
const saved = useRef(handler);
useEffect(() => { saved.current = handler; }, [handler]);
useEffect(() => {
const listener = (event: PointerEvent) => {
if (!ref.current || ref.current.contains(event.target as Node)) return;
saved.current(event);
};
document.addEventListener("pointerdown", listener);
return () => document.removeEventListener("pointerdown", listener);
}, [ref]);
}
Kiedy go nie używać: Jeśli do menu lub okna dialogowego używasz już biblioteki headless (Radix, Headless UI): obsługują za Ciebie kliknięcia na zewnątrz, Escape i powrót fokusu. Ręcznie zrobionemu menu opartemu tylko na tym hooku zwykle brakuje obsługi klawiatury.
useIntervalsetInterval, który zawsze wywołuje najnowszy callback i można go wstrzymać, przekazując null jako opóźnienie.
import { useEffect, useRef } from "react";
export function useInterval(callback: () => void, delay: number | null) {
const saved = useRef(callback);
useEffect(() => { saved.current = callback; }, [callback]);
useEffect(() => {
if (delay === null) return;
const id = window.setInterval(() => saved.current(), delay);
return () => window.clearInterval(id);
}, [delay]);
}
// useInterval(() => setTicks((t) => t + 1), running ? 1000 : null);
Kiedy go nie używać: Do animacji (użyj CSS lub requestAnimationFrame) ani do dokładnych zegarów: interwały dryfują, a przeglądarki spowalniają je w kartach w tle. Przy odliczaniu obliczaj pozostały czas przy każdym tyknięciu na podstawie stałego znacznika czasu końca.
useWindowSizeZwraca bieżącą szerokość i wysokość okna i aktualizuje się przy zmianie rozmiaru.
import { useSyncExternalStore } from "react";
function subscribe(onChange: () => void) {
window.addEventListener("resize", onChange);
return () => window.removeEventListener("resize", onChange);
}
export function useWindowSize() {
const width = useSyncExternalStore(subscribe, () => window.innerWidth, () => 0);
const height = useSyncExternalStore(subscribe, () => window.innerHeight, () => 0);
return { width, height };
}
Kiedy go nie używać: Do breakpointów: useMediaQuery renderuje ponownie tylko wtedy, gdy zapytanie zmieni wynik, a ten hook przy każdym pikselu zmiany rozmiaru. Do rozmiaru jednego elementu zamiast okna użyj ResizeObserver. (Dwie osobne migawki są celowe: zwracanie nowego obiektu z jednej funkcji migawki zapętliłoby się w nieskończoność.)
useCopyToClipboardKopiuje tekst za pomocą Clipboard API i udostępnia krótkotrwałą flagę copied do informacji zwrotnej na przycisku.
import { useCallback, useState } from "react";
export function useCopyToClipboard(resetAfter = 1500) {
const [copied, setCopied] = useState(false);
const copy = useCallback(async (text: string) => {
try {
await navigator.clipboard.writeText(text);
setCopied(true);
window.setTimeout(() => setCopied(false), resetAfter);
return true;
} catch {
setCopied(false); // insecure context or permission denied
return false;
}
}, [resetAfter]);
return [copied, copy] as const;
}
Kiedy go nie używać: Na stronach serwowanych przez zwykłe HTTP: navigator.clipboard wymaga bezpiecznego kontekstu (HTTPS lub localhost). Gdy zwraca false, zawsze pokaż rozwiązanie zastępcze, na przykład zaznaczenie tekstu.
useFetchŁaduje JSON z adresu URL, śledzi stan ładowania i błędu oraz przerywa żądanie, gdy URL się zmieni lub komponent zostanie odmontowany.
import { useEffect, useState } from "react";
type FetchState<T> = { data: T | null; error: Error | null; loading: boolean };
export function useFetch<T>(url: string | null): FetchState<T> {
const [state, setState] = useState<FetchState<T>>({ data: null, error: null, loading: !!url });
useEffect(() => {
if (!url) return;
const controller = new AbortController();
setState((s) => ({ ...s, loading: true, error: null }));
fetch(url, { signal: controller.signal })
.then((res) => {
if (!res.ok) throw new Error(`Request failed with status ${res.status}`);
return res.json() as Promise<T>;
})
.then((data) => setState({ data, error: null, loading: false }))
.catch((error: Error) => {
if (error.name === "AbortError") return; // superseded or unmounted
setState({ data: null, error, loading: false });
});
return () => controller.abort();
}, [url]);
return state;
}
Kiedy go nie używać: W większych aplikacjach. Nie ma cache, deduplikacji, ponawiania ani rewalidacji, więc dwa komponenty pytające o ten sam URL wysyłają dwa żądania. Gdy potrzebujesz czegokolwiek z tego, użyj mechanizmu ładowania danych swojego frameworka lub biblioteki danych (TanStack Query, SWR). Własna dokumentacja React daje tę samą rekomendację dla pobierania danych w efektach.
useEventListenerPodpina nasłuchiwanie zdarzenia do okna (lub dowolnego elementu) i zawsze wywołuje najnowszą funkcję obsługi, bez ponownej subskrypcji przy każdym renderze.
import { useEffect, useRef } from "react";
export function useEventListener<K extends keyof WindowEventMap>(
type: K,
handler: (event: WindowEventMap[K]) => void,
target: Window | HTMLElement | null = typeof window === "undefined" ? null : window,
) {
const saved = useRef(handler);
useEffect(() => { saved.current = handler; }, [handler]);
useEffect(() => {
if (!target) return;
const listener = (event: Event) => saved.current(event as WindowEventMap[K]);
target.addEventListener(type, listener);
return () => target.removeEventListener(type, listener);
}, [type, target]);
}
// useEventListener("keydown", (e) => { if (e.key === "Escape") close(); });
Kiedy go nie używać: Do zdarzeń, które React już obsługuje na renderowanych przez Ciebie elementach: używaj onClick, onKeyDown i podobnych bezpośrednio w JSX. Sięgaj po ten hook tylko dla window, document lub elementów zewnętrznych bibliotek.
useIsMountedZwraca funkcję, która mówi, czy komponent jest nadal na ekranie, dla starszego kodu asynchronicznego, który ustawia stan po await.
import { useCallback, useEffect, useRef } from "react";
export function useIsMounted() {
const mounted = useRef(false);
useEffect(() => {
mounted.current = true;
return () => { mounted.current = false; };
}, []);
return useCallback(() => mounted.current, []);
}
// const isMounted = useIsMounted();
// const data = await load(); if (isMounted()) setData(data);
Kiedy go nie używać: Domyślnie. React 18 usunął ostrzeżenie o ustawianiu stanu w odmontowanych komponentach, a lepszym rozwiązaniem jest anulowanie samej pracy (AbortController, jak w useFetch) lub ignorowanie nieaktualnych wyników flagą w funkcji czyszczącej efektu. Zostaw go dla starszego kodu, którego nie możesz jeszcze przebudować.
useState (lub useReducer, jeśli kilka wartości zmienia się razem).useRef.useContext lub zewnętrzny magazyn odczytywany przez useSyncExternalStore.useEffect; przechodź na useLayoutEffect tylko przy pomiarach, które powodują widoczne migotanie.useMemo. Zmemoizowane dziecko wciąż się renderuje przez prop z funkcją: useCallback.useDeferredValue lub useTransition.useActionState, useFormStatus, useOptimistic.Budujesz bibliotekę komponentów zamiast pojedynczych hooków? Nasze porównanie bibliotek UI dla React pokazuje te same sześć komponentów w shadcn/ui, Radix, Headless UI, daisyUI, Flowbite i MUI, a ściąga z Tailwind omawia klasy narzędziowe używane w demach na tej stronie. Gotowe elementy React i Tailwind znajdziesz w galerii komponentów.
W chwili pisania (październik 2026, React 19.3) dokumentacja hooków React wymienia 17 hooków w samym React: useState, useReducer, useContext, useRef, useImperativeHandle, useEffect, useLayoutEffect, useInsertionEffect, useEffectEvent, useMemo, useCallback, useTransition, useDeferredValue, useDebugValue, useId, useSyncExternalStore i useActionState. React 19 dodaje też useOptimistic i API use, a React DOM dodaje useFormStatus.
useMemo zapamiętuje wynik wywołania funkcji, a useCallback zapamiętuje samą funkcję. useCallback(fn, deps) jest równoważne useMemo(() => fn, deps). Używaj useMemo do kosztownych wartości, a useCallback do funkcji przekazywanych zmemoizowanym dzieciom lub używanych jako zależności efektów.
W trybie deweloperskim z włączonym Strict Mode React montuje każdy komponent, odmontowuje go i montuje ponownie, aby pomóc Ci znaleźć efekty bez funkcji czyszczącej. Nie dzieje się to w buildzie produkcyjnym. Jeśli podwójne uruchomienie powoduje błąd, dodaj funkcję czyszczącą, która cofa to, co efekt uruchomił.
Pusta tablica mówi React, że efekt nie odczytuje propsów ani stanu, więc uruchamia się raz po zamontowaniu komponentu, a jego funkcja czyszcząca uruchamia się przy odmontowaniu. Jeśli efekt odczytuje propsy lub stan, wymień je, inaczej będzie wciąż używał wartości z pierwszego renderu.
Nie. use, useActionState, useOptimistic i useFormStatus nie są eksportowane przez React 18, a useEffectEvent wymaga React 19.2 lub nowszego. W React 18 podobne zachowanie uzyskasz dzięki useState z obsługą zdarzeń w formularzach oraz refowi przechowującemu najnowszy callback zamiast useEffectEvent.
Jej nazwa zaczyna się od use i wywołuje co najmniej jeden inny hook. Takie nazewnictwo pozwala regułom lintera React sprawdzić, czy przestrzega zasad hooków. Każdy komponent wywołujący własny hook dostaje osobny stan; hooki współdzielą logikę, a nie dane.
Na żywo: {title}
+3 (updater)
Resetuj
Przeczytaj sekcję o useReducer
Dodaj własną pozycję
Nowa pozycja
Nowe zadanie
Usuń ukończone
Odczytuję "{theme}" z kontekstu
Przełącz wartość providera
Kliknij przycisk, aby ustawić na mnie fokus
Cel fokusu
Ustaw fokus na polu
Dodaj do refa (bez ponownego renderu)
Przerenderuj
Wartość refa pokazana przy ostatnim renderze: {n}
Pisz tutaj
Pole dziecka
Rodzic wywołuje shake()
Rodzic wywołuje clear()
efekt uruchomiony: interwał wystartował
czyszczenie uruchomione: interwał wyczyszczony
Wstrzymaj
Wznów
Zmień mój rozmiar, pisząc
Tekst do zmierzenia
Zmierzono przed malowaniem: {n}px szerokości
Kolor obramowania
Ta reguła została wstrzyknięta do head strony
Filtruj 4,000 pozycji
Filtruj pozycje
Niezwiązany render ({n})
Dopasowania: {n}. Filtr uruchomił się {runs} razy (niezwiązane rendery go nie uruchamiają).
{label} (dziecko wyrenderowane {n}x)
funkcja inline
Przerenderuj rodzica
Licznik: {n}
O nas
Wolna lista
Kontakt
Przełączanie (stara karta pozostaje klikalna)...
Wyświetlana: {tab}
Wiersz
Treść karty {tab}.
Wyniki wyrenderowane dla: "{query}"
Pisz szybko: pole pozostaje responsywne
Szukaj
Imię
Sieć:
Szerokość okna:
Zmień rozmiar okna lub wyłącz sieć, aby zobaczyć aktualizację.
Ten komponent wywołuje własny hook, który w React DevTools oznacza się etykietą "{label}". Otwórz kartę Components, aby zobaczyć etykietę obok hooka.
Online
Offline
tablice zależności
[bez tablicy] uruchomiony (render {n})
[] uruchomiony raz przy zamontowaniu
[a] uruchomiony, a = {n}
Zmień a ({n})
Zmień b ({n})
Wyczyść log
Pisz, a potem zatrzymaj się na pół sekundy
Pole z debounce
Natychmiast: "{text}" · Z debounce: "{debounced}"
Pisz, a potem przeładuj stronę
Zapisane imię
Zapisane tylko w tej przeglądarce.
Zapisane tylko w tej przeglądarce: "{name}".
useMediaQuery: viewport ma {size}, motyw systemowy to {theme}.
768px lub więcej
mniej niż 768px
Wł.
Wył.
Licznik {n}
poprzednio było {value}
jeszcze nic
Zatrzymaj
Start
Tyknięcia: {n}
Skopiowano
Skopiuj polecenie
Otwórz menu
Kliknij w dowolnym miejscu poza menu, aby je zamknąć.
Zacznij za darmo · Bez karty kredytowej