Programování

Stav React aplikace: kdy stačí Context a kdy sáhnout po Zustandu

Lokální stav, serverová data, Context a Zustand v Reactu. Jak rozhodnout, kam stav patří, jak se vyhnout zbytečným rerenderům a jak store navrhnout.

12. 10. 2026 · 5 min čtení

„Jakou knihovnu na state management?” je jedna z nejčastějších otázek v Reactu. Správná odpověď začíná jinou otázkou: jaký druh stavu to je? Velká část stavu, který lidé cpou do globálních storů, tam vůbec nepatří.

Čtyři druhy stavu

Druh Příklad Kam s ním
Lokální UI stav otevřený dropdown, hodnota inputu useState v komponentě
Serverová data seznam produktů, detail uživatele TanStack Query, SWR, nebo framework (Next.js)
Stav v URL filtr, stránka, vyhledávací dotaz query parametry (?page=2&q=kava)
Globální klientský stav přihlášený uživatel, téma, košík, otevřený panel Context nebo Zustand

Serverová data nejsou „globální stav” – je to cache dat ze serveru. Potřebuje řešit načítání, chyby, zastarání, opakování a deduplikaci. Knihovna typu TanStack Query to dělá za vás:

const { data, isPending, error } = useQuery({
  queryKey: ['products', category],
  queryFn: () => fetch(`/api/products?c=${category}`).then(r => r.json()),
});

Filtry patří do URL, aby šly sdílet odkazem a fungovalo tlačítko Zpět. Když tohle oddělíte, na globální stav často zbude jen pár věcí.

Nejdřív: posunout stav nahoru

Pokud dvě sourozenecké komponenty potřebují stejná data, přesuňte useState do jejich společného rodiče a předejte hodnotu přes props. „Prop drilling” o dvou třech úrovních není problém – je to explicitní a snadno sledovatelné.

React Context: pro málo se měnící hodnoty

Context řeší předání hodnoty hluboko do stromu bez props. Není to state management – je to mechanismus pro předání hodnoty.

// ThemeContext.jsx
import { createContext, useContext, useState, useMemo } from 'react';

const ThemeContext = createContext(null);

export function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');
  const value = useMemo(() => ({ theme, setTheme }), [theme]);
  return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
}

export function useTheme() {
  const ctx = useContext(ThemeContext);
  if (!ctx) throw new Error('useTheme musí být uvnitř ThemeProvider');
  return ctx;
}
function Toggle() {
  const { theme, setTheme } = useTheme();
  return (
    <button onClick={() => setTheme(t => (t === 'light' ? 'dark' : 'light'))}>
      Režim: {theme}
    </button>
  );
}

Vlastní hook useTheme s kontrolou zajistí srozumitelnou chybu, když komponentu použijete mimo provider. useMemo zabrání tomu, aby se při každém renderu provideru vytvořil nový objekt a překreslili se všichni konzumenti.

Hlavní omezení Contextu

Každá komponenta, která volá useContext, se překreslí při jakékoli změně hodnoty – i když používá jen její část. Context nemá selektory. Pro téma, jazyk nebo přihlášeného uživatele (mění se zřídka) je to v pořádku. Pro často se měnící stav (formulář, pozice myši, stav editoru) to znamená zbytečné rerendery velkých částí aplikace.

Částečné řešení je rozdělit Context na víc menších (zvlášť hodnota a zvlášť funkce pro změnu). Když toho začne být hodně, je to signál pro Zustand.

Zustand: malý a rychlý store

npm install zustand
// store/cart.js
import { create } from 'zustand';

export const useCartStore = create((set, get) => ({
  items: [],
  add: product =>
    set(state => {
      const existing = state.items.find(i => i.id === product.id);
      if (existing) {
        return {
          items: state.items.map(i =>
            i.id === product.id ? { ...i, qty: i.qty + 1 } : i
          ),
        };
      }
      return { items: [...state.items, { ...product, qty: 1 }] };
    }),
  remove: id => set(state => ({ items: state.items.filter(i => i.id !== id) })),
  clear: () => set({ items: [] }),
  total: () => get().items.reduce((s, i) => s + i.price * i.qty, 0),
}));

Žádný provider, store se použije přímo hookem:

function CartBadge() {
  const count = useCartStore(state => state.items.length);
  return <span className="badge">{count}</span>;
}

function AddButton({ product }) {
  const add = useCartStore(state => state.add);
  return <button onClick={() => add(product)}>Do košíku</button>;
}

Klíčová výhoda: selektor. CartBadge se překreslí jen když se změní počet položek, AddButton se nepřekreslí nikdy (funkce add je stabilní). U Contextu by se překreslily obě komponenty při každé změně košíku.

set provádí mělké sloučení s existujícím stavem, takže vracíte jen změněné klíče. Stav stejně jako v Reactu neměňte přímo – vracejte nové objekty a pole.

Selektor vracející objekt

// ❌ nový objekt při každém volání → v Zustand v5 nekonečná smyčka / zbytečné rendery
const { items, clear } = useCartStore(s => ({ items: s.items, clear: s.clear }));

// ✅
import { useShallow } from 'zustand/react/shallow';
const { items, clear } = useCartStore(useShallow(s => ({ items: s.items, clear: s.clear })));

Nebo prostě zavolejte hook dvakrát s jednoduchými selektory.

Uložení do localStorage

import { create } from 'zustand';
import { persist } from 'zustand/middleware';

export const useCartStore = create(
  persist(
    (set, get) => ({ /* … stejné jako výše … */ }),
    {
      name: 'cart',
      partialize: state => ({ items: state.items }),   // ukládat jen data, ne funkce
    }
  )
);

Při serverovém renderování (Next.js) pozor: na serveru localStorage neexistuje a první render na klientu má výchozí stav. Komponenty závislé na uloženém stavu může být potřeba vykreslit až po načtení na klientu.

Přístup mimo React

useCartStore.getState().clear();               // např. po odhlášení
const unsub = useCartStore.subscribe(s => console.log(s.items));

Hodí se pro API klienta, WebSocket handlery nebo testy.

Rozhodovací strom

  1. Používá to jen jedna komponenta? → useState.
  2. Jsou to data ze serveru? → TanStack Query / SWR / data fetching frameworku.
  3. Má to jít sdílet odkazem? → URL.
  4. Potřebují to sourozenci? → posunout stav do rodiče.
  5. Globální a mění se zřídka (téma, jazyk, uživatel)? → Context.
  6. Globální, mění se často nebo ho čte hodně komponent? → Zustand.

Redux Toolkit je stále rozumná volba pro velké týmy, které chtějí přísnou strukturu, middleware a výborné DevTools. Pro většinu menších a středních aplikací je Zustand kratší cesta ke stejnému výsledku. Zustand mimochodem umí napojit Redux DevTools přes middleware devtools.