2 min read
Web DevelopmentZustand vs. Jotai: Moderne State Management für React 2026
Vergleich der führenden React State Management Libraries. Zustand vs. Jotai - Architektur, Performance, Use Cases und Entscheidungshilfe.
ZustandJotaiReact State ManagementRedux AlternativeAtomic StateGlobal State

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
// 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
// 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
// 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
// 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
// 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
// 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
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
// 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
// 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
- "Two state management approaches - central store vs distributed atoms, architectural diagram"
- "React components with state flowing down, tree structure visualization"
- "Lightweight boxes vs heavy Redux container, bundle size comparison concept"