# Zustand vs. Jotai: Moderne State Management für React 2026 **Meta-Description:** Vergleich der führenden React State Management Libraries. Zustand vs. Jotai - Architektur, Performance, Use Cases und Entscheidungshilfe. **Keywords:** Zustand, Jotai, React State Management, Redux Alternative, Atomic State, Global State, React Context --- ## Einführung Redux-Fatigue ist real. 2026 dominieren **Zustand** und **Jotai** als leichtgewichtige, TypeScript-first Alternativen. Beide kommen von Poimandres (formerly pmndrs) – aber lösen verschiedene Probleme. --- ## Quick Comparison ``` ┌─────────────────────────────────────────────────────────────┐ │ ZUSTAND vs. JOTAI │ ├─────────────────────────────────────────────────────────────┤ │ │ │ ZUSTAND JOTAI │ │ ──────────────────── ──────────────────── │ │ Store-basiert Atom-basiert │ │ Top-Down Bottom-Up │ │ Zentraler State Dezentraler State │ │ Simpler für globalen State Flexibler für UI State │ │ │ │ Best for: Best for: │ │ • App-weiter State • Component-lokaler State │ │ • Einfache Stores • Derived State │ │ • Server State (mit Persist) • Async State │ │ • Redux-Migration • Code-Splitting │ │ │ │ Bundle: ~1.2KB Bundle: ~2.2KB │ │ │ └─────────────────────────────────────────────────────────────┘ ``` --- ## Zustand ### Basic Store ```typescript // stores/useStore.ts import { create } from 'zustand'; interface CounterState { count: number; increment: () => void; decrement: () => void; reset: () => void; } export const useCounterStore = create((set) => ({ count: 0, increment: () => set((state) => ({ count: state.count + 1 })), decrement: () => set((state) => ({ count: state.count - 1 })), reset: () => set({ count: 0 }) })); // Verwendung in Component function Counter() { const { count, increment, decrement } = useCounterStore(); return (
{count}
); } // Selective Subscription (Performance!) function CountDisplay() { const count = useCounterStore((state) => state.count); return {count}; } ``` ### Komplexer Store mit Slices ```typescript // stores/userStore.ts import { create } from 'zustand'; import { persist, devtools } from 'zustand/middleware'; interface User { id: string; name: string; email: string; } interface UserState { user: User | null; isAuthenticated: boolean; isLoading: boolean; error: string | null; login: (email: string, password: string) => Promise; logout: () => void; updateProfile: (data: Partial) => Promise; } export const useUserStore = create()( devtools( persist( (set, get) => ({ user: null, isAuthenticated: false, isLoading: false, error: null, login: async (email, password) => { set({ isLoading: true, error: null }); try { const response = await fetch('/api/auth/login', { method: 'POST', body: JSON.stringify({ email, password }) }); if (!response.ok) throw new Error('Login failed'); const user = await response.json(); set({ user, isAuthenticated: true, isLoading: false }); } catch (error) { set({ error: error.message, isLoading: false }); } }, logout: () => { set({ user: null, isAuthenticated: false }); }, updateProfile: async (data) => { const { user } = get(); if (!user) return; set({ isLoading: true }); try { const response = await fetch('/api/user/profile', { method: 'PATCH', body: JSON.stringify(data) }); const updatedUser = await response.json(); set({ user: updatedUser, isLoading: false }); } catch (error) { set({ error: error.message, isLoading: false }); } } }), { name: 'user-storage', // localStorage key partialize: (state) => ({ user: state.user }) // Nur user persistieren } ), { name: 'UserStore' } // DevTools name ) ); ``` ### Store Slices Pattern ```typescript // stores/slices/cartSlice.ts import { StateCreator } from 'zustand'; export interface CartSlice { items: CartItem[]; addItem: (item: CartItem) => void; removeItem: (id: string) => void; clearCart: () => void; totalPrice: () => number; } export const createCartSlice: StateCreator = (set, get) => ({ items: [], addItem: (item) => set((state) => ({ items: [...state.items, item] })), removeItem: (id) => set((state) => ({ items: state.items.filter((i) => i.id !== id) })), clearCart: () => set({ items: [] }), totalPrice: () => get().items.reduce((sum, item) => sum + item.price, 0) }); // stores/index.ts import { create } from 'zustand'; import { CartSlice, createCartSlice } from './slices/cartSlice'; import { UserSlice, createUserSlice } from './slices/userSlice'; type StoreState = CartSlice & UserSlice; export const useStore = create()((...a) => ({ ...createCartSlice(...a), ...createUserSlice(...a) })); ``` --- ## Jotai ### Basic Atoms ```typescript // atoms/counter.ts import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai'; // Primitive Atom export const countAtom = atom(0); // Derived Atom (Read-only) export const doubleCountAtom = atom((get) => get(countAtom) * 2); // Derived Atom (Read-Write) export const countWithDoubleAtom = atom( (get) => get(countAtom), (get, set, newValue: number) => { set(countAtom, newValue * 2); } ); // Verwendung function Counter() { const [count, setCount] = useAtom(countAtom); const doubleCount = useAtomValue(doubleCountAtom); return (
{count} (double: {doubleCount})
); } // Nur Setter (keine Re-renders bei count-Änderung) function IncrementButton() { const setCount = useSetAtom(countAtom); return ; } ``` ### Async Atoms ```typescript // atoms/user.ts import { atom } from 'jotai'; import { atomWithQuery } from 'jotai-tanstack-query'; // Async Read Atom export const userAtom = atom(async () => { const response = await fetch('/api/user'); return response.json(); }); // Mit React Query Integration export const userQueryAtom = atomWithQuery(() => ({ queryKey: ['user'], queryFn: async () => { const response = await fetch('/api/user'); return response.json(); } })); // Verwendung mit Suspense function UserProfile() { const [user] = useAtom(userAtom); // user ist bereits resolved! return
{user.name}
; } // In parent }> ``` ### Atom Families ```typescript // atoms/todos.ts import { atom } from 'jotai'; import { atomFamily } from 'jotai/utils'; interface Todo { id: string; text: string; done: boolean; } // Atom für jeden Todo (by ID) export const todoAtomFamily = atomFamily((id: string) => atom(null) ); // Liste der IDs export const todoIdsAtom = atom([]); // Derived: Alle Todos export const todosAtom = atom((get) => { const ids = get(todoIdsAtom); return ids.map(id => get(todoAtomFamily(id))).filter(Boolean); }); // Verwendung function TodoItem({ id }: { id: string }) { const [todo, setTodo] = useAtom(todoAtomFamily(id)); if (!todo) return null; return (
setTodo({ ...todo, done: !todo.done })} /> {todo.text}
); } ``` ### Persistence mit atomWithStorage ```typescript import { atomWithStorage } from 'jotai/utils'; // Automatisch in localStorage persistiert export const themeAtom = atomWithStorage<'light' | 'dark'>('theme', 'light'); export const settingsAtom = atomWithStorage('settings', { notifications: true, language: 'de' }); // Session Storage export const sessionDataAtom = atomWithStorage( 'session', null, undefined, // default serializer { getOnInit: true } ); ``` --- ## Vergleich: Gleiche Funktionalität ### Shopping Cart mit Zustand ```typescript // stores/cartStore.ts import { create } from 'zustand'; interface CartStore { items: CartItem[]; addItem: (item: CartItem) => void; removeItem: (id: string) => void; updateQuantity: (id: string, quantity: number) => void; total: number; } export const useCartStore = create((set, get) => ({ items: [], addItem: (item) => set((state) => { const existing = state.items.find(i => i.id === item.id); if (existing) { return { items: state.items.map(i => i.id === item.id ? { ...i, quantity: i.quantity + 1 } : i ) }; } return { items: [...state.items, { ...item, quantity: 1 }] }; }), removeItem: (id) => set((state) => ({ items: state.items.filter(i => i.id !== id) })), updateQuantity: (id, quantity) => set((state) => ({ items: state.items.map(i => i.id === id ? { ...i, quantity } : i ) })), get total() { return get().items.reduce( (sum, item) => sum + item.price * item.quantity, 0 ); } })); ``` ### Shopping Cart mit Jotai ```typescript // atoms/cart.ts import { atom } from 'jotai'; export const cartItemsAtom = atom([]); export const addItemAtom = atom( null, (get, set, item: CartItem) => { const items = get(cartItemsAtom); const existing = items.find(i => i.id === item.id); if (existing) { set(cartItemsAtom, items.map(i => i.id === item.id ? { ...i, quantity: i.quantity + 1 } : i )); } else { set(cartItemsAtom, [...items, { ...item, quantity: 1 }]); } } ); export const removeItemAtom = atom( null, (get, set, id: string) => { set(cartItemsAtom, get(cartItemsAtom).filter(i => i.id !== id)); } ); export const cartTotalAtom = atom((get) => { const items = get(cartItemsAtom); return items.reduce( (sum, item) => sum + item.price * item.quantity, 0 ); }); ``` --- ## Entscheidungshilfe | Kriterium | Zustand | Jotai | |-----------|---------|-------| | **Lernkurve** | Sehr einfach | Einfach | | **Bundle Size** | 1.2KB | 2.2KB | | **Globaler State** | Exzellent | Gut | | **Komponenten-State** | Gut | Exzellent | | **Derived State** | Selectors | Atoms (eleganter) | | **Async** | Middleware | Nativ | | **DevTools** | Ja | Ja | | **Redux Migration** | Einfach | Schwieriger | ### Wähle Zustand wenn: - Du einen zentralen, Redux-ähnlichen Store willst - Dein State hauptsächlich global ist - Du von Redux migrierst - Du Actions und State zusammen halten willst ### Wähle Jotai wenn: - Du viel derived/computed State hast - Du feingranulare Re-Renders brauchst - Du React Suspense für Async State nutzt - Du atomare Updates bevorzugst --- ## Fazit Beide Libraries sind exzellent. Die Wahl hängt vom Denkmodell ab: - **Zustand**: "Ein Store, viele Slices" (Top-Down) - **Jotai**: "Viele Atoms, komponiert" (Bottom-Up) Für die meisten Apps: **Zustand** für Einfachheit, **Jotai** für Flexibilität. --- ## Bildprompts 1. "Two state management approaches - central store vs distributed atoms, architectural diagram" 2. "React components with state flowing down, tree structure visualization" 3. "Lightweight boxes vs heavy Redux container, bundle size comparison concept" --- ## Quellen - [Zustand GitHub](https://github.com/pmndrs/zustand) - [Jotai Documentation](https://jotai.org/) - [Zustand vs Jotai Comparison](https://docs.pmnd.rs/zustand/getting-started/comparison) - [Jotai Tutorial](https://jotai.org/docs/basics/primitives)