Prop Drilling — problém

Prop drilling nastane, když musíte předávat data skrze více vrstev komponent, přestože mezivrstvy data vůbec nepoužívají. Výsledek: těžko udržovatelný kód.

❌ Prop Drilling
App (user state)
↓ user
Layout (user) ← nepotřebuje
↓ user
Sidebar (user) ← nepotřebuje
↓ user
UserAvatar ✅ potřebuje
✅ Context API
App (UserContext.Provider)
↓ (nezáleží na hloubce)
Layout
Sidebar
UserAvatar → useContext(UserContext)
// ❌ Prop drilling — user se předává skrze 3 vrstvy
function App() {
  const [user, setUser] = useState({ name: 'Jana', role: 'admin' });
  return <Layout user={user} setUser={setUser} />;
}
function Layout({ user, setUser }) {
  return <Sidebar user={user} setUser={setUser} />; // nezajímá ho
}
function Sidebar({ user, setUser }) {
  return <UserAvatar user={user} setUser={setUser} />; // nezajímá ho
}
function UserAvatar({ user }) { // konečně potřebuje!
  return <img src={user.avatar} alt={user.name} />;
}
// 01 / 08 Context API →

Context API

Context umožňuje sdílet data napříč stromem komponent bez prop drillingu. Tři části: createContext, Provider, useContext.

import { createContext, useContext, useState } from 'react';

// 1. Vytvořit Context (s výchozí hodnotou pro TypeScript)
const UserContext = createContext(null);

// 2. Provider — obalí strom komponent
function App() {
  const [user, setUser] = useState({ name: 'Jana', role: 'admin' });

  return (
    <UserContext.Provider value={{ user, setUser }}>
      <Layout />  {/* žádné props! */}
    </UserContext.Provider>
  );
}

// 3. useContext — libovolně hluboko
function UserAvatar() {
  const { user } = useContext(UserContext);
  return <img src={user.avatar} alt={user.name} />;
}

// TypeScript — typované Context
interface UserContextType {
  user: User | null;
  setUser: (user: User) => void;
}
const UserContext = createContext<UserContextType | undefined>(undefined);

// Custom hook pro bezpečné použití
function useUser() {
  const context = useContext(UserContext);
  if (!context) throw new Error('useUser must be used within UserProvider');
  return context;
}
⚠️
Context a performance: Každá změna hodnoty Provideru způsobí re-render VŠECH konsumentů. Pro frequently changing data (mouse position, scroll) Context není vhodný — použijte Zustand nebo rozdělte Context na menší části.
Context — Theme + Auth

// Kvíz: Kdy Context způsobí zbytečné re-rendery?

// 02 / 08 useReducer →

useReducer — komplexní stav

useReducer je alternativa k useState pro komplexní stavovou logiku. Místo přímého nastavení stavu posíláte akce a čistá funkce reducer rozhoduje, jak se stav změní.

import { useReducer } from 'react';

// Typy akcí
type Action =
  | { type: 'INCREMENT' }
  | { type: 'DECREMENT' }
  | { type: 'SET'; payload: number }
  | { type: 'RESET' };

// Reducer — čistá funkce (bez side effects!)
function reducer(state: number, action: Action): number {
  switch (action.type) {
    case 'INCREMENT': return state + 1;
    case 'DECREMENT': return state - 1;
    case 'SET':       return action.payload;
    case 'RESET':     return 0;
    default:          return state; // exhaustive check
  }
}

function Counter() {
  const [count, dispatch] = useReducer(reducer, 0);

  return (
    <div>
      <p>{count}</p>
      <button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button>
      <button onClick={() => dispatch({ type: 'DECREMENT' })}>-</button>
      <button onClick={() => dispatch({ type: 'SET', payload: 10 })}>= 10</button>
      <button onClick={() => dispatch({ type: 'RESET' })}>Reset</button>
    </div>
  );
}

// useReducer vs useState — kdy co?
// useState: jednoduché primitivy, nezávislé hodnoty
// useReducer: objekt s mnoha poli, akce mění více polí,
//             logika patří dohromady, testovatelnost
useReducer — košík e-shopu

// Kvíz: Musí být reducer v useReducer čistá funkce (pure function)?

Context + Reducer — profesionální pattern

Kombinace Context + useReducer je nejčastěji doporučovaný pattern pro sdílený stav v mid-size React aplikacích bez externích knihoven.

// store/kosik.tsx — kompletní store module

// Typy
interface KosikItem { id: number; nazev: string; cena: number; mnozstvi: number; }
interface KosikState { polozky: KosikItem[]; isOpen: boolean; }
type KosikAction =
  | { type: 'PRIDAT';   produkt: Omit<KosikItem, 'mnozstvi'> }
  | { type: 'ODEBRAT';  id: number }
  | { type: 'TOGGLE_KOSIK' };

// Reducer
function kosikReducer(state: KosikState, action: KosikAction): KosikState {
  switch (action.type) {
    case 'PRIDAT': {
      const exists = state.polozky.find(i => i.id === action.produkt.id);
      return {
        ...state,
        polozky: exists
          ? state.polozky.map(i => i.id === action.produkt.id
              ? { ...i, mnozstvi: i.mnozstvi + 1 } : i)
          : [...state.polozky, { ...action.produkt, mnozstvi: 1 }],
      };
    }
    case 'ODEBRAT':
      return { ...state, polozky: state.polozky.filter(i => i.id !== action.id) };
    case 'TOGGLE_KOSIK':
      return { ...state, isOpen: !state.isOpen };
    default: return state;
  }
}

// Context
const KosikContext = createContext<{
  state: KosikState;
  dispatch: Dispatch<KosikAction>;
} | undefined>(undefined);

// Provider
export function KosikProvider({ children }: { children: ReactNode }) {
  const [state, dispatch] = useReducer(
    kosikReducer,
    { polozky: [], isOpen: false }
  );
  return (
    <KosikContext.Provider value={{ state, dispatch }}>
      {children}
    </KosikContext.Provider>
  );
}

// Custom hook (s type safety)
export function useKosik() {
  const ctx = useContext(KosikContext);
  if (!ctx) throw new Error('useKosik mimo KosikProvider');
  return ctx;
}

// Použití kdekoliv v app:
const { state, dispatch } = useKosik();
// 04 / 08 Zustand →

Zustand — minimalistický store

Zustand (německy "stav") je nejpopulárnější lightweight state management library pro React. Žádný boilerplate, žádný Provider, žádné actions/reducers — jen store objekt.

// npm install zustand

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

// Definice store
interface KosikStore {
  polozky: KosikItem[];
  pridat:  (produkt: Product) => void;
  odebrat: (id: number)       => void;
  vymazat: ()                 => void;
  celkem:  () => number;
}

const useKosik = create<KosikStore>()(
  persist(  // ← automaticky persist do localStorage!
    (set, get) => ({
      polozky: [],

      pridat: (produkt) => set((state) => {
        const existuje = state.polozky.find(i => i.id === produkt.id);
        return {
          polozky: existuje
            ? state.polozky.map(i => i.id === produkt.id
                ? { ...i, mnozstvi: i.mnozstvi + 1 } : i)
            : [...state.polozky, { ...produkt, mnozstvi: 1 }],
        };
      }),

      odebrat: (id) => set((state) => ({
        polozky: state.polozky.filter(i => i.id !== id),
      })),

      vymazat: () => set({ polozky: [] }),

      celkem: () => get().polozky.reduce(
        (sum, i) => sum + i.cena * i.mnozstvi, 0
      ),
    }),
    { name: 'kosik-storage' }  // localStorage key
  )
);

// Použití — anywhere, no Provider!
function ProductCard({ product }) {
  const pridat = useKosik(state => state.pridat); // selector — jen co potřebuji
  return <button onClick={() => pridat(product)}>Přidat</button>;
}

function CartBadge() {
  const count = useKosik(state => state.polozky.length);
  return <span>{count}</span>; // re-renderuje jen při změně count!
}
Zustand simulace (vanilla JS store pattern)
// 05 / 08 Kdy co použít →

Kdy co použít

SituaceŘešeníProč
Stav jedné komponentyuseStateJednoduché, lokální
Komplexní logika, více akcíuseReducerPřehledné akce, testovatelné
Sdílená data (téma, auth, jazyk)Context + useStateSem-tam čtené, málo změn
Sdílená data + komplexní akceContext + useReducerStrukturované, bez deps
Globální, často měněný stavZustandSelektory, bez Provider, výkon
Server state (cache, refetch)TanStack QueryCaching, invalidace, sync (lekce 8)
Velká enterprise appRedux ToolkitDevTools, middleware, time-travel

// Kvíz: Jaká je hlavní výhoda Zustand oproti Context API pro výkonnostně kritický stav?

// 06 / 08 Cvičení →

Cvičení — NotificationContext

Napište NotificationContext — globální toast notifikace. Provider udrží seznam notifikací (text, typ, id), funkce addNotification přidá novou (auto-mizí po 3s), removeNotification odebere.

Vaše řešení — NotificationContext
🏅

State Management Expert

Zvládáte Context, useReducer, Zustand patterns a víte kdy co použít.

// 07 / 08 Taháček →

// Taháček

// Context
const Ctx = createContext(null);
<Ctx.Provider value={...}>{children}</Ctx.Provider>
const value = useContext(Ctx);

// useReducer
const [state, dispatch] = useReducer(reducer, initialState);
dispatch({ type: 'ACTION', payload: data });

// Context + Reducer provider pattern
function Provider({ children }) {
  const [state, dispatch] = useReducer(reducer, init);
  return <Ctx.Provider value={{ state, dispatch }}>{children}</Ctx.Provider>;
}

// Zustand store
const useStore = create((set, get) => ({
  data: [],
  add:    (item) => set(s => ({ data: [...s.data, item] })),
  remove: (id)   => set(s => ({ data: s.data.filter(i => i.id !== id) })),
  total:  ()     => get().data.length,
}));
// Použití: const data = useStore(s => s.data); // selector!
// Lekce 3 dokončena L4: Next.js →