Skip to content

Instantly share code, notes, and snippets.

@mosioc
Last active June 22, 2026 19:38
Show Gist options
  • Select an option

  • Save mosioc/8205471f30fcd5dc1bd06cfcbeb7df63 to your computer and use it in GitHub Desktop.

Select an option

Save mosioc/8205471f30fcd5dc1bd06cfcbeb7df63 to your computer and use it in GitHub Desktop.
Zustand cheat sheet

Zustand Cheat Sheet

A minimal, unopinionated state management library for React with a hooks-based API.


Core Concepts

  • Store: A single source of truth holding your application state
  • Hooks-based: Access state using hooks, no providers needed
  • Immutable updates: State changes require creating new objects/arrays
  • Selectors: Functions to extract specific slices of state
  • Middleware: Enhance stores with persistence, devtools, immer, etc.
  • No boilerplate: Minimal setup compared to Redux or Context API

Core Idea (Mental Model)

                ┌──────────────────────────────┐
                │        ZUSTAND STORE         │
                │──────────────────────────────│
                │ state                        │
                │ actions (set / get)          │
                │ listeners (subscriptions)    │
                └─────────────┬────────────────┘
                              │
        set()                 │            useStore(selector)
   (update state)             │        (subscribe to slice only)
                              │
                              ▼
                ┌──────────────────────────────┐
                │        REACT COMPONENTS      │
                │──────────────────────────────│
                │ useStore(selector)           │
                │ → subscribes to slice only   │
                │ → re-renders selectively     │
                └──────────────────────────────┘

Full State Flow

1.  create((set, get) => ({ state + actions }))

                ↓

2.  Component subscribes
    useStore(state => state.count)

                ↓

3.  Zustand registers listener
    listeners.add(component)

                ↓

4.  Action triggers update
    set(state => newState)

                ↓

5.  Store updates state

                ↓

6.  Notify only affected subscribers

                ↓

7.  React re-renders ONLY those components

Installation

npm install zustand
# or
yarn add zustand

Basic Store Creation

Simple Store

import { create } from 'zustand'

const useStore = create((set) => ({
  // State
  count: 0,
  
  // Actions
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
  reset: () => set({ count: 0 })
}))

Explanation:

  • create() returns a hook
  • set() merges partial state (shallow merge)
  • Actions are functions that call set() to update state
  • Arrow functions in set() receive current state

Using the Store

function Counter() {
  const count = useStore((state) => state.count)
  const increment = useStore((state) => state.increment)
  
  return (
    <div>
      <p>{count}</p>
      <button onClick={increment}>+</button>
    </div>
  )
}

Explanation:

  • Call the hook with a selector function
  • Component re-renders only when selected state changes
  • Multiple selectors = multiple subscriptions

State Updates

Using set()

// Partial update (shallow merge)
set({ count: 1 })

// Function form (access current state)
set((state) => ({ count: state.count + 1 }))

// Replace entire state (use carefully)
set({ count: 0 }, true) // second param = replace

Using get()

const useStore = create((set, get) => ({
  count: 0,
  increment: () => {
    const current = get().count
    set({ count: current + 1 })
  }
}))

Explanation: get() retrieves current state synchronously within actions


Selectors

Basic Selector

const count = useStore((state) => state.count)

Multiple Values

// ❌ Causes re-renders on any state change
const { count, user } = useStore((state) => state)

// ✅ Only re-renders when count OR user changes
const count = useStore((state) => state.count)
const user = useStore((state) => state.user)

// ✅ Use shallow equality for objects
import { shallow } from 'zustand/shallow'
const { count, user } = useStore(
  (state) => ({ count: state.count, user: state.user }),
  shallow
)

Explanation:

  • Default equality: Object.is() (strict equality)
  • Destructuring entire state causes unnecessary re-renders
  • shallow compares object properties for equality

Derived State

const useStore = create((set) => ({
  items: [],
  addItem: (item) => set((state) => ({ 
    items: [...state.items, item] 
  }))
}))

// Computed value (memoized automatically)
const itemCount = useStore((state) => state.items.length)

Async Actions

const useStore = create((set) => ({
  users: [],
  loading: false,
  
  fetchUsers: async () => {
    set({ loading: true })
    const response = await fetch('/api/users')
    const users = await response.json()
    set({ users, loading: false })
  }
}))

Explanation:

  • Actions can be async functions
  • Call set() multiple times to update loading states
  • No special async middleware needed

Nested State Updates

Manual Spreading

const useStore = create((set) => ({
  user: { name: 'John', age: 30 },
  
  updateName: (name) => set((state) => ({
    user: { ...state.user, name }
  }))
}))

With Immer Middleware

import { immer } from 'zustand/middleware/immer'

const useStore = create(
  immer((set) => ({
    user: { name: 'John', age: 30 },
    
    updateName: (name) => set((state) => {
      state.user.name = name // Direct mutation with Immer
    })
  }))
)

Explanation: Immer allows "mutative" syntax while maintaining immutability


Middleware

Persist (localStorage/sessionStorage)

import { persist } from 'zustand/middleware'

const useStore = create(
  persist(
    (set) => ({
      count: 0,
      increment: () => set((state) => ({ count: state.count + 1 }))
    }),
    {
      name: 'counter-storage', // localStorage key
      // storage: sessionStorage, // optional
    }
  )
)

DevTools

import { devtools } from 'zustand/middleware'

const useStore = create(
  devtools(
    (set) => ({
      count: 0,
      increment: () => set((state) => ({ count: state.count + 1 }))
    }),
    { name: 'CounterStore' }
  )
)

Combining Middleware

const useStore = create(
  devtools(
    persist(
      immer((set) => ({
        // store definition
      })),
      { name: 'storage-key' }
    ),
    { name: 'StoreName' }
  )
)

Explanation: Middleware wraps stores, order matters (outer middleware wraps inner)


TypeScript Support

Basic Typing

interface StoreState {
  count: number
  increment: () => void
  decrement: () => void
}

const useStore = create<StoreState>((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 }))
}))

With Middleware

import { persist, devtools } from 'zustand/middleware'

interface StoreState {
  count: number
  increment: () => void
}

const useStore = create<StoreState>()(
  devtools(
    persist(
      (set) => ({
        count: 0,
        increment: () => set((state) => ({ count: state.count + 1 }))
      }),
      { name: 'counter' }
    )
  )
)

Explanation: Notice create<Type>()() - double invocation needed with middleware


Slices Pattern (Code Organization)

const createUserSlice = (set) => ({
  user: null,
  setUser: (user) => set({ user })
})

const createSettingsSlice = (set) => ({
  theme: 'light',
  setTheme: (theme) => set({ theme })
})

const useStore = create((set) => ({
  ...createUserSlice(set),
  ...createSettingsSlice(set)
}))

Explanation: Split large stores into logical slices for better organization


Accessing Store Outside React

const useStore = create((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 }))
}))

// Get current state
const count = useStore.getState().count

// Subscribe to changes
const unsubscribe = useStore.subscribe(
  (state) => console.log('State changed:', state)
)

// Update state
useStore.getState().increment()

// Cleanup
unsubscribe()

Explanation: Useful for utilities, API interceptors, or non-React code


Best Practices

  • Keep stores flat: Avoid deeply nested state when possible
  • Single store vs multiple stores: Use single store for related data, multiple stores for independent domains
  • Colocate actions with state: Define actions inside the store creation
  • Use selectors wisely: Select only what you need to minimize re-renders
  • Avoid mixing concerns: Don't put UI state and server state in the same store
  • Name actions clearly: Use verbs (increment, fetchUsers, setTheme)
  • Use middleware for cross-cutting concerns: Persistence, logging, devtools
  • TypeScript: Always type your stores for better DX

Common Mistakes

Mistake Why It's Wrong Solution
const state = useStore() Subscribes to entire store, re-renders on any change Use selectors: useStore(s => s.count)
Mutating state directly Breaks immutability, no re-renders Use set() with new objects/arrays
set({ items: state.items.push(x) }) push() returns length, not array set(s => ({ items: [...s.items, x] }))
Using get() in components Not reactive, won't trigger re-renders Use hook with selector instead
Not using shallow for object selectors Causes re-renders even when values unchanged Import and use shallow equality
Forgetting async handling UI doesn't reflect loading states Set loading flags before/after async calls

Performance Optimization

Prevent Unnecessary Re-renders

// ❌ New object every render
const data = useStore((state) => ({ 
  count: state.count, 
  name: state.name 
}))

// ✅ Use shallow equality
import { shallow } from 'zustand/shallow'
const data = useStore(
  (state) => ({ count: state.count, name: state.name }),
  shallow
)

// ✅ Or select primitives separately
const count = useStore((state) => state.count)
const name = useStore((state) => state.name)

Subscribe to Specific Changes

const useStore = create(
  subscribeWithSelector((set) => ({
    count: 0,
    user: null
  }))
)

// Only runs when count changes
useEffect(() => {
  const unsubscribe = useStore.subscribe(
    (state) => state.count,
    (count) => console.log('Count:', count)
  )
  return unsubscribe
}, [])

Explanation: subscribeWithSelector middleware enables granular subscriptions


Quick Reference / TL;DR

// 1. Create store
import { create } from 'zustand'
const useStore = create((set, get) => ({
  count: 0,
  increment: () => set((s) => ({ count: s.count + 1 }))
}))

// 2. Use in component
const count = useStore((state) => state.count)
const increment = useStore((state) => state.increment)

// 3. Multiple values with shallow
import { shallow } from 'zustand/shallow'
const { count, user } = useStore(
  (s) => ({ count: s.count, user: s.user }),
  shallow
)

// 4. Persist
import { persist } from 'zustand/middleware'
const useStore = create(persist(
  (set) => ({ /* ... */ }),
  { name: 'storage-key' }
))

// 5. Outside React
useStore.getState().increment()
const unsub = useStore.subscribe(console.log)
@mosioc

mosioc commented Jun 22, 2026

Copy link
Copy Markdown
Author

Zustand: Complete Breakdown for React/Next.js

1. High-Level Overview

┌─────────────────────────────────────────────────────────────┐
│                   ZUSTAND ARCHITECTURE                      │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌──────────────────────────────────────────────────────┐  │
│  │              STORE CREATION                          │  │
│  │  create() → Returns hook → Components consume        │  │
│  └──────────────────────────────────────────────────────┘  │
│                          ↓                                  │
│  ┌──────────────────────────────────────────────────────┐  │
│  │         STATE CONTAINER (Vanilla JS)                 │  │
│  │  ┌────────────┐  ┌────────────┐  ┌──────────────┐  │  │
│  │  │   State    │  │  Actions   │  │  Selectors   │  │  │
│  │  │ (Immutable)│  │ (Mutators) │  │  (Derived)   │  │  │
│  │  └────────────┘  └────────────┘  └──────────────┘  │  │
│  └──────────────────────────────────────────────────────┘  │
│                          ↓                                  │
│  ┌──────────────────────────────────────────────────────┐  │
│  │           SUBSCRIPTION SYSTEM                        │  │
│  │  Components subscribe → State changes → Re-render    │  │
│  └──────────────────────────────────────────────────────┘  │
│                                                             │
│  ┌──────────────────────────────────────────────────────┐  │
│  │              KEY FEATURES                            │  │
│  ├──────────────────────────────────────────────────────┤  │
│  │ • No Context Provider needed                         │  │
│  │ • No boilerplate (actions, reducers, types)          │  │
│  │ • Minimal re-renders (granular subscriptions)        │  │
│  │ • Works outside React (vanilla store)                │  │
│  │ • Async actions built-in                             │  │
│  │ • Middleware support (persist, devtools, immer)      │  │
│  │ • TypeScript-first                                   │  │
│  └──────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘

What is Zustand?

  • German for "state"
  • Minimalist state management library (~1kb)
  • No providers, no reducers, no actions creators
  • Direct state mutation with automatic immutability
  • Works with React, vanilla JS, and other frameworks

2. Core Concepts

2.1 Basic Store Creation

npm install zustand
// store/useCounterStore.ts
import { create } from 'zustand';

// Define store interface
interface CounterState {
  count: number;
  increment: () => void;
  decrement: () => void;
  reset: () => void;
  setCount: (count: number) => void;
}

// Create store
const useCounterStore = create<CounterState>((set) => ({
  // Initial state
  count: 0,
  
  // Actions (state updaters)
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
  reset: () => set({ count: 0 }),
  setCount: (count) => set({ count }),
}));

export default useCounterStore;

How it works:

create() function
    ↓
Returns a React hook (useCounterStore)
    ↓
Components call hook to access state/actions
    ↓
set() function updates state immutably
    ↓
Subscribed components automatically re-render

2.2 Using the Store in Components

'use client';

import useCounterStore from '@/store/useCounterStore';

export default function Counter() {
  // Subscribe to entire store (re-renders on any state change)
  const { count, increment, decrement, reset } = useCounterStore();
  
  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={increment}>+1</button>
      <button onClick={decrement}>-1</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
}

// ✅ BETTER: Subscribe to specific state (granular)
export default function CounterOptimized() {
  // Only re-renders when count changes
  const count = useCounterStore((state) => state.count);
  const increment = useCounterStore((state) => state.increment);
  
  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={increment}>+1</button>
    </div>
  );
}

// ✅ EVEN BETTER: Use shallow equality for multiple values
import { shallow } from 'zustand/shallow';

export default function CounterBest() {
  const { count, increment } = useCounterStore(
    (state) => ({ count: state.count, increment: state.increment }),
    shallow
  );
  
  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={increment}>+1</button>
    </div>
  );
}

Subscription Patterns:

// ❌ BAD: Re-renders on ANY state change
const store = useCounterStore();

// ✅ GOOD: Re-renders only when count changes
const count = useCounterStore((state) => state.count);

// ✅ GOOD: Re-renders only when name or email changes
const { name, email } = useUserStore(
  (state) => ({ name: state.name, email: state.email }),
  shallow
);

2.3 The set Function Deep Dive

import { create } from 'zustand';

const useStore = create((set) => ({
  count: 0,
  user: { name: 'John', age: 30 },
  todos: [],
  
  // 1. Replace state (merge)
  updateCount: (newCount: number) => set({ count: newCount }),
  
  // 2. Update based on previous state
  increment: () => set((state) => ({ count: state.count + 1 })),
  
  // 3. Update nested state (must spread)
  updateUserName: (name: string) => 
    set((state) => ({ user: { ...state.user, name } })),
  
  // 4. Array operations
  addTodo: (todo: Todo) => 
    set((state) => ({ todos: [...state.todos, todo] })),
  
  removeTodo: (id: string) =>
    set((state) => ({ todos: state.todos.filter(t => t.id !== id) })),
  
  updateTodo: (id: string, updates: Partial<Todo>) =>
    set((state) => ({
      todos: state.todos.map(t => t.id === id ? { ...t, ...updates } : t)
    })),
  
  // 5. Replace entire state (use with caution)
  reset: () => set({ count: 0, user: { name: '', age: 0 }, todos: [] }, true),
  //                                                                    ↑ replace flag
}));

set() Signatures:

// Merge update (default)
set({ count: 5 })

// Function update (access previous state)
set((state) => ({ count: state.count + 1 }))

// Replace entire state (lose all other keys)
set({ count: 0 }, true)

2.4 The get Function

const useStore = create((set, get) => ({
  count: 0,
  multiplier: 2,
  
  // Access current state within actions
  incrementByMultiplier: () => {
    const { count, multiplier } = get();
    set({ count: count + multiplier });
  },
  
  // Derived computations
  getDoubledCount: () => {
    return get().count * 2;
  },
  
  // Complex logic
  canAddTodo: () => {
    const { todos } = get();
    return todos.length < 10;
  },
  
  addTodo: (todo: Todo) => {
    if (get().canAddTodo()) {
      set((state) => ({ todos: [...state.todos, todo] }));
    } else {
      console.error('Max todos reached');
    }
  }
}));

3. Real-World Store Examples

3.1 Authentication Store

// store/useAuthStore.ts
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';

interface User {
  id: string;
  email: string;
  name: string;
  avatar?: string;
}

interface AuthState {
  user: User | null;
  token: string | null;
  isAuthenticated: boolean;
  isLoading: boolean;
  
  login: (email: string, password: string) => Promise<void>;
  logout: () => void;
  updateProfile: (updates: Partial<User>) => void;
  checkAuth: () => Promise<void>;
}

const useAuthStore = create<AuthState>()(
  persist(
    (set, get) => ({
      user: null,
      token: null,
      isAuthenticated: false,
      isLoading: false,
      
      login: async (email, password) => {
        set({ isLoading: true });
        
        try {
          const response = await fetch('/api/auth/login', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ email, password })
          });
          
          if (!response.ok) throw new Error('Login failed');
          
          const { user, token } = await response.json();
          
          set({
            user,
            token,
            isAuthenticated: true,
            isLoading: false
          });
        } catch (error) {
          set({ isLoading: false });
          throw error;
        }
      },
      
      logout: () => {
        set({
          user: null,
          token: null,
          isAuthenticated: false
        });
      },
      
      updateProfile: (updates) => {
        const currentUser = get().user;
        if (!currentUser) return;
        
        set({
          user: { ...currentUser, ...updates }
        });
      },
      
      checkAuth: async () => {
        const token = get().token;
        if (!token) return;
        
        try {
          const response = await fetch('/api/auth/me', {
            headers: { Authorization: `Bearer ${token}` }
          });
          
          if (!response.ok) {
            get().logout();
            return;
          }
          
          const user = await response.json();
          set({ user, isAuthenticated: true });
        } catch (error) {
          get().logout();
        }
      }
    }),
    {
      name: 'auth-storage',
      storage: createJSONStorage(() => localStorage),
      partialize: (state) => ({ 
        user: state.user, 
        token: state.token 
      }), // Only persist specific fields
    }
  )
);

export default useAuthStore;

Usage:

'use client';

import useAuthStore from '@/store/useAuthStore';

export default function LoginForm() {
  const { login, isLoading } = useAuthStore();
  
  const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    const formData = new FormData(e.currentTarget);
    
    try {
      await login(
        formData.get('email') as string,
        formData.get('password') as string
      );
      
      // Redirect after successful login
      window.location.href = '/dashboard';
    } catch (error) {
      alert('Login failed');
    }
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <input name="email" type="email" required />
      <input name="password" type="password" required />
      <button type="submit" disabled={isLoading}>
        {isLoading ? 'Logging in...' : 'Login'}
      </button>
    </form>
  );
}

// Protected route component
export function ProtectedPage() {
  const { isAuthenticated, user } = useAuthStore();
  
  if (!isAuthenticated) {
    return <div>Please log in</div>;
  }
  
  return <div>Welcome, {user?.name}!</div>;
}

3.2 Shopping Cart Store

// store/useCartStore.ts
import { create } from 'zustand';
import { persist } from 'zustand/middleware';

interface CartItem {
  id: string;
  name: string;
  price: number;
  quantity: number;
  image: string;
}

interface CartState {
  items: CartItem[];
  
  // Computed values
  totalItems: number;
  totalPrice: number;
  
  // Actions
  addItem: (item: Omit<CartItem, 'quantity'>) => void;
  removeItem: (id: string) => void;
  updateQuantity: (id: string, quantity: number) => void;
  clearCart: () => void;
  
  // Helper methods
  getItem: (id: string) => CartItem | undefined;
  hasItem: (id: string) => boolean;
}

const useCartStore = create<CartState>()(
  persist(
    (set, get) => ({
      items: [],
      
      // Computed properties (recalculated on access)
      get totalItems() {
        return get().items.reduce((sum, item) => sum + item.quantity, 0);
      },
      
      get totalPrice() {
        return get().items.reduce(
          (sum, item) => sum + item.price * item.quantity, 
          0
        );
      },
      
      addItem: (newItem) => {
        const items = get().items;
        const existingItem = items.find(item => item.id === newItem.id);
        
        if (existingItem) {
          // Increment quantity if item already exists
          set({
            items: items.map(item =>
              item.id === newItem.id
                ? { ...item, quantity: item.quantity + 1 }
                : item
            )
          });
        } else {
          // Add new item
          set({
            items: [...items, { ...newItem, quantity: 1 }]
          });
        }
      },
      
      removeItem: (id) => {
        set({
          items: get().items.filter(item => item.id !== id)
        });
      },
      
      updateQuantity: (id, quantity) => {
        if (quantity <= 0) {
          get().removeItem(id);
          return;
        }
        
        set({
          items: get().items.map(item =>
            item.id === id ? { ...item, quantity } : item
          )
        });
      },
      
      clearCart: () => set({ items: [] }),
      
      getItem: (id) => {
        return get().items.find(item => item.id === id);
      },
      
      hasItem: (id) => {
        return get().items.some(item => item.id === id);
      }
    }),
    {
      name: 'shopping-cart',
    }
  )
);

export default useCartStore;

Usage:

'use client';

import useCartStore from '@/store/useCartStore';

// Product card
export function ProductCard({ product }: { product: Product }) {
  const addItem = useCartStore((state) => state.addItem);
  const hasItem = useCartStore((state) => state.hasItem(product.id));
  
  return (
    <div>
      <img src={product.image} alt={product.name} />
      <h3>{product.name}</h3>
      <p>${product.price}</p>
      <button 
        onClick={() => addItem(product)}
        disabled={hasItem}
      >
        {hasItem ? 'In Cart' : 'Add to Cart'}
      </button>
    </div>
  );
}

// Cart summary
export function CartSummary() {
  const { totalItems, totalPrice } = useCartStore();
  
  return (
    <div>
      <p>Items: {totalItems}</p>
      <p>Total: ${totalPrice.toFixed(2)}</p>
    </div>
  );
}

// Cart items list
export function CartItemsList() {
  const items = useCartStore((state) => state.items);
  const updateQuantity = useCartStore((state) => state.updateQuantity);
  const removeItem = useCartStore((state) => state.removeItem);
  
  return (
    <div>
      {items.map(item => (
        <div key={item.id}>
          <img src={item.image} alt={item.name} />
          <h4>{item.name}</h4>
          <p>${item.price}</p>
          
          <input
            type="number"
            value={item.quantity}
            onChange={(e) => updateQuantity(item.id, parseInt(e.target.value))}
            min="1"
          />
          
          <button onClick={() => removeItem(item.id)}>Remove</button>
        </div>
      ))}
    </div>
  );
}

3.3 Todo Store with Advanced Features

// store/useTodoStore.ts
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';

interface Todo {
  id: string;
  title: string;
  description: string;
  completed: boolean;
  priority: 'low' | 'medium' | 'high';
  dueDate: Date | null;
  tags: string[];
  createdAt: Date;
}

type Filter = 'all' | 'active' | 'completed';
type SortBy = 'createdAt' | 'dueDate' | 'priority';

interface TodoState {
  todos: Todo[];
  filter: Filter;
  sortBy: SortBy;
  searchQuery: string;
  
  // Actions
  addTodo: (todo: Omit<Todo, 'id' | 'createdAt'>) => void;
  toggleTodo: (id: string) => void;
  deleteTodo: (id: string) => void;
  updateTodo: (id: string, updates: Partial<Todo>) => void;
  
  // Filters and sorting
  setFilter: (filter: Filter) => void;
  setSortBy: (sortBy: SortBy) => void;
  setSearchQuery: (query: string) => void;
  
  // Selectors
  getFilteredTodos: () => Todo[];
  getTodoById: (id: string) => Todo | undefined;
  getActiveTodosCount: () => number;
  getCompletedTodosCount: () => number;
}

const useTodoStore = create<TodoState>()(
  devtools(
    persist(
      immer((set, get) => ({
        todos: [],
        filter: 'all',
        sortBy: 'createdAt',
        searchQuery: '',
        
        addTodo: (todoData) => {
          set((state) => {
            state.todos.push({
              ...todoData,
              id: crypto.randomUUID(),
              createdAt: new Date()
            });
          }, false, 'addTodo'); // Action name for devtools
        },
        
        toggleTodo: (id) => {
          set((state) => {
            const todo = state.todos.find(t => t.id === id);
            if (todo) {
              todo.completed = !todo.completed;
            }
          }, false, 'toggleTodo');
        },
        
        deleteTodo: (id) => {
          set((state) => {
            state.todos = state.todos.filter(t => t.id !== id);
          }, false, 'deleteTodo');
        },
        
        updateTodo: (id, updates) => {
          set((state) => {
            const todo = state.todos.find(t => t.id === id);
            if (todo) {
              Object.assign(todo, updates);
            }
          }, false, 'updateTodo');
        },
        
        setFilter: (filter) => set({ filter }),
        setSortBy: (sortBy) => set({ sortBy }),
        setSearchQuery: (searchQuery) => set({ searchQuery }),
        
        // Derived data (computed on-demand)
        getFilteredTodos: () => {
          const { todos, filter, sortBy, searchQuery } = get();
          
          let filtered = todos;
          
          // Apply filter
          if (filter === 'active') {
            filtered = filtered.filter(t => !t.completed);
          } else if (filter === 'completed') {
            filtered = filtered.filter(t => t.completed);
          }
          
          // Apply search
          if (searchQuery) {
            filtered = filtered.filter(t =>
              t.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
              t.description.toLowerCase().includes(searchQuery.toLowerCase())
            );
          }
          
          // Apply sorting
          filtered.sort((a, b) => {
            if (sortBy === 'createdAt') {
              return b.createdAt.getTime() - a.createdAt.getTime();
            } else if (sortBy === 'dueDate') {
              if (!a.dueDate) return 1;
              if (!b.dueDate) return -1;
              return a.dueDate.getTime() - b.dueDate.getTime();
            } else if (sortBy === 'priority') {
              const priorityOrder = { high: 0, medium: 1, low: 2 };
              return priorityOrder[a.priority] - priorityOrder[b.priority];
            }
            return 0;
          });
          
          return filtered;
        },
        
        getTodoById: (id) => {
          return get().todos.find(t => t.id === id);
        },
        
        getActiveTodosCount: () => {
          return get().todos.filter(t => !t.completed).length;
        },
        
        getCompletedTodosCount: () => {
          return get().todos.filter(t => t.completed).length;
        }
      })),
      {
        name: 'todo-storage',
        // Custom serialization for Date objects
        serialize: (state) => JSON.stringify(state, (key, value) =>
          value instanceof Date ? value.toISOString() : value
        ),
        deserialize: (str) => {
          const parsed = JSON.parse(str);
          parsed.state.todos = parsed.state.todos.map((todo: any) => ({
            ...todo,
            createdAt: new Date(todo.createdAt),
            dueDate: todo.dueDate ? new Date(todo.dueDate) : null
          }));
          return parsed;
        }
      }
    ),
    { name: 'TodoStore' }
  )
);

export default useTodoStore;

Usage:

'use client';

import useTodoStore from '@/store/useTodoStore';

export default function TodoApp() {
  const {
    filter,
    sortBy,
    searchQuery,
    setFilter,
    setSortBy,
    setSearchQuery,
    addTodo,
    toggleTodo,
    deleteTodo,
    getFilteredTodos,
    getActiveTodosCount
  } = useTodoStore();
  
  const todos = getFilteredTodos();
  const activeCount = getActiveTodosCount();
  
  const handleAddTodo = (title: string) => {
    addTodo({
      title,
      description: '',
      completed: false,
      priority: 'medium',
      dueDate: null,
      tags: []
    });
  };
  
  return (
    <div>
      <h1>Todos ({activeCount} active)</h1>
      
      {/* Search */}
      <input
        type="text"
        value={searchQuery}
        onChange={(e) => setSearchQuery(e.target.value)}
        placeholder="Search todos..."
      />
      
      {/* Filters */}
      <div>
        {(['all', 'active', 'completed'] as const).map(f => (
          <button
            key={f}
            onClick={() => setFilter(f)}
            className={filter === f ? 'active' : ''}
          >
            {f}
          </button>
        ))}
      </div>
      
      {/* Sort */}
      <select value={sortBy} onChange={(e) => setSortBy(e.target.value as SortBy)}>
        <option value="createdAt">Created Date</option>
        <option value="dueDate">Due Date</option>
        <option value="priority">Priority</option>
      </select>
      
      {/* Todo list */}
      <ul>
        {todos.map(todo => (
          <li key={todo.id}>
            <input
              type="checkbox"
              checked={todo.completed}
              onChange={() => toggleTodo(todo.id)}
            />
            <span>{todo.title}</span>
            <button onClick={() => deleteTodo(todo.id)}>Delete</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

4. Middleware

4.1 Persist Middleware

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

interface Store {
  count: number;
  increment: () => void;
}

// LocalStorage (default)
const useStore = create<Store>()(
  persist(
    (set) => ({
      count: 0,
      increment: () => set((state) => ({ count: state.count + 1 }))
    }),
    {
      name: 'counter-storage', // Key in localStorage
    }
  )
);

// SessionStorage
const useSessionStore = create<Store>()(
  persist(
    (set) => ({
      count: 0,
      increment: () => set((state) => ({ count: state.count + 1 }))
    }),
    {
      name: 'counter-storage',
      storage: createJSONStorage(() => sessionStorage),
    }
  )
);

// IndexedDB (for large data)
import { get, set, del } from 'idb-keyval';

const useIndexedDBStore = create<Store>()(
  persist(
    (set) => ({
      count: 0,
      increment: () => set((state) => ({ count: state.count + 1 }))
    }),
    {
      name: 'counter-storage',
      storage: {
        getItem: async (name) => {
          return (await get(name)) || null;
        },
        setItem: async (name, value) => {
          await set(name, value);
        },
        removeItem: async (name) => {
          await del(name);
        },
      },
    }
  )
);

// Partial persistence (only save specific fields)
const usePartialPersist = create<Store>()(
  persist(
    (set) => ({
      count: 0,
      tempData: 'not persisted',
      increment: () => set((state) => ({ count: state.count + 1 }))
    }),
    {
      name: 'counter-storage',
      partialize: (state) => ({ count: state.count }), // Only persist count
    }
  )
);

// Migration (handle version changes)
const useMigratedStore = create<Store>()(
  persist(
    (set) => ({
      count: 0,
      increment: () => set((state) => ({ count: state.count + 1 }))
    }),
    {
      name: 'counter-storage',
      version: 2,
      migrate: (persistedState: any, version: number) => {
        if (version === 0) {
          // Migrate from v0 to v1
          persistedState.count = persistedState.count || 0;
        }
        if (version === 1) {
          // Migrate from v1 to v2
          persistedState.newField = 'default';
        }
        return persistedState;
      },
    }
  )
);

4.2 DevTools Middleware

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

const useStore = create<Store>()(
  devtools(
    (set) => ({
      count: 0,
      increment: () => set((state) => ({ count: state.count + 1 }), false, 'increment'),
      //                                                            ↑       ↑
      //                                                       replace   action name
      decrement: () => set((state) => ({ count: state.count - 1 }), false, 'decrement'),
      reset: () => set({ count: 0 }, false, 'reset'),
    }),
    {
      name: 'CounterStore', // Name in Redux DevTools
      enabled: process.env.NODE_ENV === 'development', // Only in dev
    }
  )
);

// Combined with persist
const useCombinedStore = create<Store>()(
  devtools(
    persist(
      (set) => ({
        count: 0,
        increment: () => set((state) => ({ count: state.count + 1 }))
      }),
      { name: 'counter-storage' }
    ),
    { name: 'CounterStore' }
  )
);

4.3 Immer Middleware (Mutable Updates)

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

interface Todo {
  id: string;
  title: string;
  completed: boolean;
  nested: {
    deep: {
      value: number;
    };
  };
}

interface Store {
  todos: Todo[];
  addTodo: (todo: Todo) => void;
  toggleTodo: (id: string) => void;
  updateNested: (id: string, value: number) => void;
}

const useStore = create<Store>()(
  immer((set) => ({
    todos: [],
    
    // ✅ Write mutable code, Immer handles immutability
    addTodo: (todo) => set((state) => {
      state.todos.push(todo); // Direct mutation!
    }),
    
    toggleTodo: (id) => set((state) => {
      const todo = state.todos.find(t => t.id === id);
      if (todo) {
        todo.completed = !todo.completed; // Direct mutation!
      }
    }),
    
    updateNested: (id, value) => set((state) => {
      const todo = state.todos.find(t => t.id === id);
      if (todo) {
        todo.nested.deep.value = value; // Nested mutation!
      }
    }),
  }))
);

// Without Immer (manual immutability):
const useStoreWithoutImmer = create<Store>((set) => ({
  todos: [],
  
  addTodo: (todo) => set((state) => ({
    todos: [...state.todos, todo]
  })),
  
  toggleTodo: (id) => set((state) => ({
    todos: state.todos.map(t =>
      t.id === id ? { ...t, completed: !t.completed } : t
    )
  })),
  
  updateNested: (id, value) => set((state) => ({
    todos: state.todos.map(t =>
      t.id === id
        ? {
            ...t,
            nested: {
              ...t.nested,
              deep: {
                ...t.nested.deep,
                value
              }
            }
          }
        : t
    )
  })),
}));

4.4 Custom Middleware

import { StateCreator, StoreMutatorIdentifier } from 'zustand';

// Logger middleware
const logger = <T>(config: StateCreator<T>): StateCreator<T> => {
  return (set, get, api) =>
    config(
      (args) => {
        console.log('  Prev state:', get());
        set(args);
        console.log('  New state:', get());
      },
      get,
      api
    );
};

// Usage
const useStore = create<Store>()(
  logger((set) => ({
    count: 0,
    increment: () => set((state) => ({ count: state.count + 1 }))
  }))
);

// Reset middleware (adds reset function to all stores)
type Reset = {
  reset: () => void;
};

const resetters: (() => void)[] = [];

const reset = <T>(config: StateCreator<T>): StateCreator<T & Reset> => {
  return (set, get, api) => {
    const initialState = config(set, get, api);
    
    resetters.push(() => {
      set(initialState as any);
    });
    
    return {
      ...initialState,
      reset: () => set(initialState as any),
    };
  };
};

// Global reset all stores
export const resetAllStores = () => {
  resetters.forEach(resetter => resetter());
};

// Usage
const useStore = create<Store>()(
  reset((set) => ({
    count: 0,
    increment: () => set((state) => ({ count: state.count + 1 }))
  }))
);

// In component
const reset = useStore((state) => state.reset);

5. Advanced Patterns

5.1 Slices Pattern (Modular Stores)

// slices/authSlice.ts
import { StateCreator } from 'zustand';

export interface AuthSlice {
  user: User | null;
  token: string | null;
  login: (email: string, password: string) => Promise<void>;
  logout: () => void;
}

export const createAuthSlice: StateCreator
  AuthSlice & CartSlice & UISlice, // All slices combined
  [],
  [],
  AuthSlice // This slice
> = (set) => ({
  user: null,
  token: null,
  
  login: async (email, password) => {
    const { user, token } = await loginAPI(email, password);
    set({ user, token });
  },
  
  logout: () => set({ user: null, token: null }),
});

// slices/cartSlice.ts
export interface CartSlice {
  items: CartItem[];
  addItem: (item: CartItem) => void;
  removeItem: (id: string) => void;
}

export const createCartSlice: StateCreator
  AuthSlice & CartSlice & UISlice,
  [],
  [],
  CartSlice
> = (set) => ({
  items: [],
  
  addItem: (item) => set((state) => ({
    items: [...state.items, item]
  })),
  
  removeItem: (id) => set((state) => ({
    items: state.items.filter(i => i.id !== id)
  })),
});

// slices/uiSlice.ts
export interface UISlice {
  theme: 'light' | 'dark';
  sidebarOpen: boolean;
  toggleTheme: () => void;
  toggleSidebar: () => void;
}

export const createUISlice: StateCreator
  AuthSlice & CartSlice & UISlice,
  [],
  [],
  UISlice
> = (set) => ({
  theme: 'light',
  sidebarOpen: false,
  
  toggleTheme: () => set((state) => ({
    theme: state.theme === 'light' ? 'dark' : 'light'
  })),
  
  toggleSidebar: () => set((state) => ({
    sidebarOpen: !state.sidebarOpen
  })),
});

// store/index.ts
import { create } from 'zustand';
import { createAuthSlice, AuthSlice } from './slices/authSlice';
import { createCartSlice, CartSlice } from './slices/cartSlice';
import { createUISlice, UISlice } from './slices/uiSlice';

type Store = AuthSlice & CartSlice & UISlice;

const useStore = create<Store>()((...a) => ({
  ...createAuthSlice(...a),
  ...createCartSlice(...a),
  ...createUISlice(...a),
}));

export default useStore;

5.2 Async Actions & Loading States

import { create } from 'zustand';

interface User {
  id: string;
  name: string;
}

interface UserState {
  users: User[];
  isLoading: boolean;
  error: string | null;
  
  fetchUsers: () => Promise<void>;
  createUser: (name: string) => Promise<void>;
  deleteUser: (id: string) => Promise<void>;
}

const useUserStore = create<UserState>((set, get) => ({
  users: [],
  isLoading: false,
  error: null,
  
  fetchUsers: async () => {
    set({ isLoading: true, error: null });
    
    try {
      const response = await fetch('/api/users');
      if (!response.ok) throw new Error('Failed to fetch users');
      
      const users = await response.json();
      set({ users, isLoading: false });
    } catch (error) {
      set({
        error: error instanceof Error ? error.message : 'Unknown error',
        isLoading: false
      });
    }
  },
  
  createUser: async (name) => {
    set({ isLoading: true, error: null });
    
    try {
      const response = await fetch('/api/users', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ name })
      });
      
      if (!response.ok) throw new Error('Failed to create user');
      
      const newUser = await response.json();
      
      set((state) => ({
        users: [...state.users, newUser],
        isLoading: false
      }));
    } catch (error) {
      set({
        error: error instanceof Error ? error.message : 'Unknown error',
        isLoading: false
      });
    }
  },
  
  deleteUser: async (id) => {
    // Optimistic update
    const previousUsers = get().users;
    set((state) => ({
      users: state.users.filter(u => u.id !== id)
    }));
    
    try {
      const response = await fetch(`/api/users/${id}`, {
        method: 'DELETE'
      });
      
      if (!response.ok) throw new Error('Failed to delete user');
    } catch (error) {
      // Rollback on error
      set({
        users: previousUsers,
        error: error instanceof Error ? error.message : 'Unknown error'
      });
    }
  }
}));

// Usage
export function UserList() {
  const { users, isLoading, error, fetchUsers } = useUserStore();
  
  useEffect(() => {
    fetchUsers();
  }, [fetchUsers]);
  
  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;
  
  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

5.3 Computed Values (Selectors)

import { create } from 'zustand';

interface Product {
  id: string;
  name: string;
  price: number;
  category: string;
  inStock: boolean;
}

interface ProductState {
  products: Product[];
  searchQuery: string;
  selectedCategory: string | null;
  priceRange: [number, number];
  
  setSearchQuery: (query: string) => void;
  setSelectedCategory: (category: string | null) => void;
  setPriceRange: (range: [number, number]) => void;
}

const useProductStore = create<ProductState>((set) => ({
  products: [],
  searchQuery: '',
  selectedCategory: null,
  priceRange: [0, 1000],
  
  setSearchQuery: (searchQuery) => set({ searchQuery }),
  setSelectedCategory: (selectedCategory) => set({ selectedCategory }),
  setPriceRange: (priceRange) => set({ priceRange }),
}));

// ✅ Create selectors outside the store
export const selectFilteredProducts = (state: ProductState) => {
  return state.products.filter(product => {
    const matchesSearch = product.name
      .toLowerCase()
      .includes(state.searchQuery.toLowerCase());
    
    const matchesCategory =
      !state.selectedCategory || product.category === state.selectedCategory;
    
    const matchesPrice =
      product.price >= state.priceRange[0] &&
      product.price <= state.priceRange[1];
    
    const inStock = product.inStock;
    
    return matchesSearch && matchesCategory && matchesPrice && inStock;
  });
};

export const selectCategories = (state: ProductState) => {
  return Array.from(new Set(state.products.map(p => p.category)));
};

export const selectTotalValue = (state: ProductState) => {
  return state.products.reduce((sum, p) => sum + p.price, 0);
};

// Usage
export function ProductList() {
  // Only re-renders when filtered products change
  const filteredProducts = useProductStore(selectFilteredProducts);
  
  return (
    <div>
      {filteredProducts.map(product => (
        <ProductCard key={product.id} product={product} />
      ))}
    </div>
  );
}

export function CategoryFilter() {
  const categories = useProductStore(selectCategories);
  const setSelectedCategory = useProductStore(state => state.setSelectedCategory);
  
  return (
    <select onChange={(e) => setSelectedCategory(e.target.value || null)}>
      <option value="">All Categories</option>
      {categories.map(cat => (
        <option key={cat} value={cat}>{cat}</option>
      ))}
    </select>
  );
}

5.4 Outside React Usage (Vanilla Store)

import { createStore } from 'zustand/vanilla';

// Create vanilla store (no React hook)
const store = createStore<CounterState>((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
}));

// Use anywhere (Node.js, Web Workers, vanilla JS)
console.log(store.getState().count); // 0

store.getState().increment();
console.log(store.getState().count); // 1

// Subscribe to changes
const unsubscribe = store.subscribe(
  (state) => console.log('Count changed:', state.count)
);

store.getState().increment(); // Logs: "Count changed: 2"

unsubscribe(); // Stop listening

// Create React hook from vanilla store
import { useStore } from 'zustand';

export const useCounterStore = () => useStore(store);

// Or with selector
export const useCount = () => useStore(store, state => state.count);

6. Next.js Integration

6.1 Server-Side Initialization

// store/useHydrationStore.ts
'use client';

import { create } from 'zustand';
import { useEffect } from 'react';

interface HydrationStore {
  serverData: any;
  isHydrated: boolean;
  setServerData: (data: any) => void;
}

const useHydrationStore = create<HydrationStore>((set) => ({
  serverData: null,
  isHydrated: false,
  setServerData: (serverData) => set({ serverData, isHydrated: true }),
}));

// Hook to initialize store from server
export function useHydration(initialData: any) {
  useEffect(() => {
    useHydrationStore.getState().setServerData(initialData);
  }, [initialData]);
}

export default useHydrationStore;

Usage in Server Component:

// app/page.tsx
import HydrationWrapper from '@/components/HydrationWrapper';

export default async function Page() {
  // Fetch data on server
  const data = await fetch('https://api.example.com/data').then(r => r.json());
  
  return <HydrationWrapper initialData={data} />;
}

// components/HydrationWrapper.tsx
'use client';

import { useHydration } from '@/store/useHydrationStore';
import ClientComponent from './ClientComponent';

export default function HydrationWrapper({ initialData }: { initialData: any }) {
  useHydration(initialData);
  
  return <ClientComponent />;
}

// components/ClientComponent.tsx
'use client';

import useHydrationStore from '@/store/useHydrationStore';

export default function ClientComponent() {
  const { serverData, isHydrated } = useHydrationStore();
  
  if (!isHydrated) return <div>Loading...</div>;
  
  return <div>{JSON.stringify(serverData)}</div>;
}

6.2 Preventing Hydration Mismatch

'use client';

import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { useEffect, useState } from 'react';

const useStore = create(
  persist(
    (set) => ({
      count: 0,
      increment: () => set((state) => ({ count: state.count + 1 }))
    }),
    { name: 'counter-storage' }
  )
);

// ✅ Prevent hydration mismatch
export function Counter() {
  const [hydrated, setHydrated] = useState(false);
  
  useEffect(() => {
    setHydrated(true);
  }, []);
  
  const count = useStore((state) => state.count);
  const increment = useStore((state) => state.increment);
  
  if (!hydrated) {
    // Return static content during SSR
    return <div>Count: 0</div>;
  }
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
    </div>
  );
}

// Or create a reusable hook
export function useHydratedStore<T, F>(
  store: (selector: (state: T) => F) => F,
  selector: (state: T) => F
) {
  const [hydrated, setHydrated] = useState(false);
  const value = store(selector);
  
  useEffect(() => {
    setHydrated(true);
  }, []);
  
  return hydrated ? value : undefined;
}

// Usage
export function CounterWithHook() {
  const count = useHydratedStore(useStore, state => state.count);
  
  if (count === undefined) return <div>Loading...</div>;
  
  return <div>Count: {count}</div>;
}

7. Testing

import { renderHook, act } from '@testing-library/react';
import { create } from 'zustand';

describe('Counter Store', () => {
  it('increments count', () => {
    const useStore = create<CounterState>((set) => ({
      count: 0,
      increment: () => set((state) => ({ count: state.count + 1 }))
    }));
    
    const { result } = renderHook(() => useStore());
    
    expect(result.current.count).toBe(0);
    
    act(() => {
      result.current.increment();
    });
    
    expect(result.current.count).toBe(1);
  });
  
  it('resets store', () => {
    const useStore = create<CounterState>((set) => ({
      count: 5,
      reset: () => set({ count: 0 })
    }));
    
    const { result } = renderHook(() => useStore());
    
    act(() => {
      result.current.reset();
    });
    
    expect(result.current.count).toBe(0);
  });
});

8. Performance Optimization

8.1 Selective Re-rendering

// ❌ BAD: Re-renders on any state change
function Component() {
  const store = useStore();
  return <div>{store.someValue}</div>;
}

// ✅ GOOD: Only re-renders when someValue changes
function Component() {
  const someValue = useStore((state) => state.someValue);
  return <div>{someValue}</div>;
}

// ✅ BETTER: Multiple values with shallow comparison
import { shallow } from 'zustand/shallow';

function Component() {
  const { value1, value2 } = useStore(
    (state) => ({ value1: state.value1, value2: state.value2 }),
    shallow
  );
  return <div>{value1} {value2}</div>;
}

8.2 Memoized Selectors

import { useMemo } from 'react';

// Memoize complex selectors
function useFilteredItems(category: string) {
  return useStore(
    useMemo(
      () => (state) => state.items.filter(item => item.category === category),
      [category]
    )
  );
}

9. Migration Guide

From Redux to Zustand

// Redux
const initialState = { count: 0 };

function counterReducer(state = initialState, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { ...state, count: state.count + 1 };
    case 'DECREMENT':
      return { ...state, count: state.count - 1 };
    default:
      return state;
  }
}

const store = createStore(counterReducer);

// Zustand (much simpler!)
const useStore = create((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
}));

From Context to Zustand

// Context (verbose)
const CountContext = createContext();

function CountProvider({ children }) {
  const [count, setCount] = useState(0);
  const increment = () => setCount(c => c + 1);
  
  return (
    <CountContext.Provider value={{ count, increment }}>
      {children}
    </CountContext.Provider>
  );
}

// Zustand (concise)
const useCountStore = create((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 }))
}));

// No provider needed!

10. Best Practices Summary

DO:

  • Use selectors for specific state slices
  • Keep stores focused and modular
  • Use middleware appropriately
  • Test stores independently
  • Persist only necessary data
  • Use TypeScript for type safety

DON'T:

  • Subscribe to entire store in components
  • Put all state in one giant store
  • Forget to handle async errors
  • Store derived data (use selectors)
  • Mutate state directly (without immer)

Zustand provides a simple, powerful, and flexible state management solution that eliminates Redux boilerplate while maintaining all the benefits of centralized state. Its small bundle size, TypeScript support, and middleware ecosystem make it ideal for modern React applications.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment