# 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<CounterState>((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 (
    <div>
      <span>{count}</span>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
    </div>
  );
}

// Selective Subscription (Performance!)
function CountDisplay() {
  const count = useCounterStore((state) => state.count);
  return <span>{count}</span>;
}
```

### 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<void>;
  logout: () => void;
  updateProfile: (data: Partial<User>) => Promise<void>;
}

export const useUserStore = create<UserState>()(
  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<CartSlice> = (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<StoreState>()((...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 (
    <div>
      <span>{count} (double: {doubleCount})</span>
      <button onClick={() => setCount(count + 1)}>+</button>
    </div>
  );
}

// Nur Setter (keine Re-renders bei count-Änderung)
function IncrementButton() {
  const setCount = useSetAtom(countAtom);
  return <button onClick={() => setCount(c => c + 1)}>+</button>;
}
```

### 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 <div>{user.name}</div>;
}

// In parent
<Suspense fallback={<Loading />}>
  <UserProfile />
</Suspense>
```

### 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<Todo | null>(null)
);

// Liste der IDs
export const todoIdsAtom = atom<string[]>([]);

// 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 (
    <div>
      <input
        type="checkbox"
        checked={todo.done}
        onChange={() => setTodo({ ...todo, done: !todo.done })}
      />
      {todo.text}
    </div>
  );
}
```

### 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<CartStore>((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<CartItem[]>([]);

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)
