Programování

React hooky prakticky: useState, useEffect, useRef a vlastní hooky

Praktický průvodce React hooky s příklady z reálných aplikací, častými chybami v useEffect a návodem, jak napsat vlastní hook.

12. 10. 2026 · 4 min čtení

Hooky jsou základ moderního Reactu. Dokumentace je vysvětluje dobře, ale v praxi se nejvíc chyb dělá v detailech: zastaralé hodnoty, nekonečné smyčky v useEffect nebo zbytečné rerendery. Projdeme hooky na konkrétních příkladech a ukážeme, kde se nejčastěji chybuje.

useState: stav komponenty

import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);
  return (
    <button onClick={() => setCount(c => c + 1)}>
      Kliknuto {count}×
    </button>
  );
}

Dvě pravidla, která ušetří hodiny ladění:

1. Když nová hodnota závisí na předchozí, použijte funkci. setCount(count + 1) třikrát za sebou v jednom handleru zvýší hodnotu jen o jedna, protože count je v rámci renderu konstanta. setCount(c => c + 1) funguje vždy správně.

2. Stav neměňte přímo. U objektů a polí vytvářejte nové kopie:

setUser(u => ({ ...u, name: 'Jana' }));
setItems(items => [...items, newItem]);
setItems(items => items.filter(i => i.id !== id));

items.push(x); setItems(items) nespustí rerender, protože React porovná referenci a ta je stejná.

Drahý výpočet počátečního stavu předejte jako funkci, aby se nespouštěl při každém renderu: useState(() => JSON.parse(localStorage.getItem('cart') ?? '[]')).

useEffect: synchronizace s vnějším světem

useEffect slouží k synchronizaci komponenty se systémem mimo React – API, časovače, event listenery, DOM knihovny. Není to „místo, kam dát kód, který se má spustit”.

useEffect(() => {
  const onResize = () => setWidth(window.innerWidth);
  window.addEventListener('resize', onResize);
  return () => window.removeEventListener('resize', onResize);
}, []);

Funkce vrácená z efektu je cleanup – React ji zavolá před dalším spuštěním efektu a při odpojení komponenty. Bez ní vám listenery a intervaly zůstanou viset.

Načítání dat bez race condition

useEffect(() => {
  const controller = new AbortController();
  setLoading(true);

  fetch(`/api/users/${userId}`, { signal: controller.signal })
    .then(r => r.json())
    .then(data => setUser(data))
    .catch(err => {
      if (err.name !== 'AbortError') setError(err);
    })
    .finally(() => setLoading(false));

  return () => controller.abort();
}, [userId]);

Když se userId rychle změní, starý požadavek se zruší a jeho odpověď nepřepíše novější data. V React 18+ ve vývojovém režimu se StrictMode efekty spouští dvakrát právě proto, abyste chybějící cleanup odhalili.

Pro větší aplikace zvažte knihovnu jako TanStack Query, která řeší cache, opakování a deduplikaci požadavků za vás.

Kdy useEffect nepotřebujete

  • Odvozené hodnoty počítejte přímo při renderu: const fullName = first + ' ' + last; – žádný stav ani efekt.
  • Reakce na akci uživatele patří do event handleru, ne do efektu, který sleduje stav.
  • Reset stavu při změně props – místo efektu dejte komponentě key={userId}.

Pole závislostí

Do pole patří všechno z komponenty, co efekt používá. Nainstalujte eslint-plugin-react-hooks a pravidlo react-hooks/exhaustive-deps vás upozorní. Pokud linter chce přidat funkci, která způsobuje nekonečnou smyčku, přesuňte ji dovnitř efektu nebo mimo komponentu.

useRef: hodnota, která nespouští render

function SearchBox() {
  const inputRef = useRef(null);

  useEffect(() => {
    inputRef.current?.focus();
  }, []);

  return <input ref={inputRef} />;
}

useRef má dvě použití: odkaz na DOM prvek a uchování hodnoty mezi rendery bez rerenderu (ID časovače, předchozí hodnota, počítadlo). Změna ref.current komponentu nepřekreslí – pokud se má hodnota zobrazit, patří do stavu.

useMemo a useCallback: až když je důvod

const filtered = useMemo(
  () => products.filter(p => p.name.includes(query)),
  [products, query]
);

const handleSelect = useCallback(id => setSelected(id), []);

Memoizace má smysl, když je výpočet opravdu drahý, nebo když předáváte funkci či objekt komponentě obalené React.memo nebo do pole závislostí efektu. Obalovat vše „pro jistotu” kód zhoršuje čitelnost a nic nezrychlí. Pokud používáte React Compiler (stabilní od roku 2025), dělá většinu memoizace automaticky.

Vlastní hook: znovupoužitelná logika

Vlastní hook je obyčejná funkce, jejíž název začíná use a která volá jiné hooky:

function useLocalStorage(key, initial) {
  const [value, setValue] = useState(() => {
    try {
      const stored = localStorage.getItem(key);
      return stored !== null ? JSON.parse(stored) : initial;
    } catch {
      return initial;
    }
  });

  useEffect(() => {
    try {
      localStorage.setItem(key, JSON.stringify(value));
    } catch {
      /* plné úložiště nebo privátní režim */
    }
  }, [key, value]);

  return [value, setValue];
}

// použití
const [theme, setTheme] = useLocalStorage('theme', 'light');

Každá komponenta, která hook zavolá, dostane vlastní nezávislý stav. Hooky sdílí logiku, ne data.

Další užitečný příklad – debounce pro vyhledávání:

function useDebounce(value, delay = 300) {
  const [debounced, setDebounced] = useState(value);
  useEffect(() => {
    const t = setTimeout(() => setDebounced(value), delay);
    return () => clearTimeout(t);
  }, [value, delay]);
  return debounced;
}

Pravidla hooků

  1. Volejte je jen na nejvyšší úrovni komponenty nebo vlastního hooku – ne v podmínkách, cyklech ani po return.
  2. Volejte je jen z React komponent nebo vlastních hooků.

React rozlišuje hooky podle pořadí volání, proto podmíněné volání rozbije stav.

Rychlá diagnostika

Příznak Pravděpodobná příčina
Nekonečná smyčka požadavků objekt/funkce v závislostech vzniká při každém renderu
Zobrazuje se stará hodnota chybějící závislost nebo stale closure v intervalu
Warning o aktualizaci odpojené komponenty / zmatená data chybí cleanup nebo AbortController
Seznam se nepřekreslí mutace pole místo nové kopie

Když se zaseknete na konkrétní komponentě, nahrajte ji do projektu v ColdHost AI a nechte si ji v režimu Programování projít – AI umí soubor rovnou upravit a změny pak exportujete nebo pošlete jako pull request na GitHub.