Ściąga z hooków React: każdy hook z przykładem na żywo

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.

Wszystkie wbudowane hooki w skrócie

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.

Wyświetlono wszystkie 20
HookGrupaDo czego służyOd wersji
useStateStanprzechowuje wartość, której zmiana ponownie renderuje komponentReact 18
useReducerStanzarządza stanem z wieloma powiązanymi aktualizacjami przez jedną funkcję reduceraReact 18
useContextKontekstodczytuje wartość z najbliższego providera powyżejReact 18
useRefRefprzechowuje zmienną wartość lub węzeł DOM, który nie wywołuje renderówReact 18
useImperativeHandleRefwybiera, które metody rodzic może wywołać przez refReact 18
useEffectEfektysynchronizuje z czymś poza React po namalowaniuReact 18
useLayoutEffectEfektymierzy układ, zanim przeglądarka namaluje stronęReact 18
useInsertionEffectEfektywstawia reguły stylów przed efektami układu (biblioteki CSS-in-JS)React 18
useEffectEventEfektyodczytuje najnowsze propsy wewnątrz efektu bez jego ponownego uruchamianiaReact 19.2
useMemoWydajnośćzapamiętuje kosztowne obliczenie między renderamiReact 18
useCallbackWydajnośćzapamiętuje funkcję, aby zmemoizowane dzieci nie renderowały się ponownieReact 18
useTransitionWydajnośćoznacza aktualizację stanu jako niepilną i daje flagę oczekiwaniaReact 18
useDeferredValueWydajnośćpozwala wolnej części UI zostawać w tyle za szybkim pisaniemReact 18
useIdInnegeneruje stabilne, unikalne ID dla etykiet i atrybutów ARIAReact 18
useSyncExternalStoreInnesubskrybuje magazyn danych działający poza ReactReact 18
useDebugValueInnenadaje etykietę własnemu hookowi w React DevToolsReact 18
useActionStateInneśledzi wynik i stan oczekiwania akcji formularzaReact 19
useOptimisticInnepokazuje oczekiwany wynik akcji, zanim się zakończyReact 19
useAPIodczytuje promise lub kontekst, nawet wewnątrz warunkówReact 19
useFormStatusReact DOModczytuje stan oczekiwania formularza nadrzędnegoReact 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.

Hooki stanu

useState

Co 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" }).

useReducer

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

Hooki kontekstu

useContext

Co 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ą.

Hooki refów

useRef

Co 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ą.

useImperativeHandle

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

Hooki efektów

useEffect

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

useLayoutEffect

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

useInsertionEffect

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

useEffectEvent

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

Hooki wydajności

useMemo

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

useCallback

Co 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ść.

useTransition

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

useDeferredValue

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

Inne hooki

useId

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

useSyncExternalStore

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

useDebugValue

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

Hooki React 19 (tylko kod, nie działają w React 18)

Tylko React 19. Cztery hooki z tej sekcji (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.

useActionState

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

useOptimistic

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

use

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

useFormStatus

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

Tablica zależności w praktyce

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:

PiszeszEfekt uruchamia sięTypowe użycie
useEffect(fn)po każdym renderzerzadkie; zwykle znak, że coś powinno być obliczane podczas renderu
useEffect(fn, [])raz po zamontowaniu (dwa razy w trybie deweloperskim Strict Mode), czyszczenie przy odmontowaniusubskrypcje 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 bpobieranie 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.

Pięć zasad, które zapobiegają większości błędów zależności

  1. Wymień każdą reaktywną wartość, którą odczytuje callback. Propsy, stan i wszystko zadeklarowane w ciele komponentu. Pominięcie którejś daje nieaktualne domknięcie: callback zachowuje wartość ze starego renderu. Zainstaluj eslint-plugin-react-hooks i pozwól, by exhaustive-deps powiedział Ci, czego brakuje.
  2. Obiekty i funkcje tworzone podczas renderu są za każdym razem nowe. { 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.
  3. Tam, gdzie się da, zależ od wartości prymitywnych. [user.id] jest stabilniejsze niż [user], gdy liczy się tylko ID.
  4. Używaj funkcji aktualizujących, aby pozbyć się zależności od stanu. setCount(c => c + 1) nie odczytuje count, więc nie trzeba wymieniać count.
  5. Nie wyciszaj lintera, aby "naprawić" pętlę. Nieskończona pętla zwykle oznacza, że efekt ustawia stan, który jest też zależnością, albo zależy od nowego obiektu. Napraw przyczynę. Dla wartości, które chcesz odczytać bez reagowania na nie, użyj 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]);

12 własnych hooków do skopiowania do Twojego projektu

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

1. useDebounce

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

2. useLocalStorage

Dział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.

3. useMediaQuery

Zwraca 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ś.

4. useToggle

Wartość 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.

5. usePrevious

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

6. useOnClickOutside

Wywoł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.

7. useInterval

setInterval, 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.

8. useWindowSize

Zwraca 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ść.)

9. useCopyToClipboard

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

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

11. useEventListener

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

12. useIsMounted

Zwraca 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ć.

Którego hooka potrzebuję? Szybka lista decyzyjna

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.

Najczęściej zadawane pytania

Ile wbudowanych hooków ma React?

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.

Jaka jest różnica między useMemo a useCallback?

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.

Dlaczego mój useEffect uruchamia się dwa razy?

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

Co oznacza pusta tablica zależności w useEffect?

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.

Czy mogę używać hooków React 19, takich jak use czy useActionState, w React 18?

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.

Co sprawia, że funkcja jest własnym hookiem?

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ę

E-mail

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

Powiązane: Poradniki

Produkt

Dla kogo

Funkcje

Zasoby

Firma

Mapa strony