Module 9: Global State Management & Mastering Zustand

🎯 Learning Objectives
- State Philosophy: Grasp when to use
useState, Context API, or Zustand. - Bearbones API: Set up a global store in just 30 seconds.
- Auto-Generating Selectors: Achieve automatic render performance optimization.
- Middleware Power: Persist your state to LocalStorage indefinitely.
- Async Actions: Handle asynchronous operations (like API calls) directly within your store.
1. State Philosophy: Local vs. Global

Not every piece of state needs to live in a Global Store. Overusing global state can make your application difficult to debug and lead to unnecessary re-renders.
| State Type | Characteristics | Solution |
|---|---|---|
| Local State | Used only within a single component or its direct children. (e.g., Modal visibility, input field values, toggles) | useState, useReducer |
| Global State | Shared across many, geographically distant parts of your app. (e.g., User sessions, themes, shopping carts) | Zustand, Context API |
| Server State | Data fetched from APIs, requiring caching and re-fetching. (e.g., Product lists, user profiles) | TanStack Query (Avoid using Zustand for this unless your app is very small) |
2. Bearbones API: Lightning-Fast Stores
Zustand (German for “State”) is incredibly minimalist. Forget Provider components and boilerplate code like you’d find in Redux.

import { create } from 'zustand';
// 1. Define the Interface (TypeScript)
interface BearState {
bears: number;
increase: () => void;
removeAll: () => void;
}
// 2. Create the Store
export const useBearStore = create<BearState>((set) => ({
bears: 0,
increase: () => set((state) => ({ bears: state.bears + 1 })),
removeAll: () => set({ bears: 0 }),
}));Use it anywhere:
import { useBearStore } from './useBearStore';
function BearCounter() {
// Select state (Best Practice: Atomic Selector)
const bears = useBearStore((state) => state.bears);
return <h1>{bears} around here...</h1>;
}
function Controls() {
// Select actions
const increase = useBearStore((state) => state.increase);
return <button onClick={increase}>One up</button>;
}3. Auto-Generating Selectors (Advanced)
Typing (state) => state.value repeatedly can become tedious. You can automatically generate use.x hooks to cut your code in half.

Helper Utility (src/lib/createSelectors.ts):
import { StoreApi, UseBoundStore } from 'zustand';
type WithSelectors<S> = S extends { getState: () => infer T } ? S & { use: { [K in keyof T]: () => T[K] } } : never;
const createSelectors = <S extends UseBoundStore<StoreApi<object>>>(_store: S) => {
let store = _store as WithSelectors<typeof _store>;
store.use = {};
for (let k of Object.keys(store.getState())) {
(store.use as any)[k] = () => store((s) => s[k as keyof typeof s]);
}
return store;
};
export default createSelectors;Applying it to Your Store:
import { create } from 'zustand';
import createSelectors from '../lib/createSelectors';
interface SettingsState {
theme: 'dark' | 'light';
toggle: () => void;
}
const useSettingsBase = create<SettingsState>((set) => ({
theme: 'dark',
toggle: () => set((s) => ({ theme: s.theme === 'dark' ? 'light' : 'dark' })),
}));
// Wrap the store
export const useSettingsStore = createSelectors(useSettingsBase);Using it (Super concise):
// ❌ Old way: const theme = useSettingsStore((state) => state.theme)
// ✅ New way: Each field automatically gets its own hook!
const theme = useSettingsStore.use.theme();
const toggle = useSettingsStore.use.toggle(); // Note: You still call the action directly4. Middleware Power: Persist (Save to LocalStorage)
Automatically save your state (like your shopping cart or theme settings) to LocalStorage so it’s not lost when you refresh the page.

import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
interface CartState {
items: string[];
isLoading: boolean; // This state should NOT be persisted
addItem: (item: string) => void;
}
export const useCartStore = create<CartState>()(
persist(
(set) => ({
items: [],
isLoading: false,
addItem: (item) => set((state) => ({ items: [...state.items, item] })),
}),
{
name: 'cart-storage', // Key name in LocalStorage
storage: createJSONStorage(() => localStorage),
// ✅ Partialize: Only persist 'items', exclude 'isLoading'
partialize: (state) => ({ items: state.items }),
}
)
);5. Async Actions
Zustand handles async/await natively, eliminating the need for complex middleware like redux-thunk or redux-saga.

interface AuthState {
user: User | null;
isLoading: boolean;
login: (email: string) => Promise<void>;
}
export const useAuthStore = create<AuthState>((set) => ({
user: null,
isLoading: false,
login: async (email) => {
set({ isLoading: true }); // 1. Start loading
try {
const response = await fetch('/api/login', { method: 'POST', body: JSON.stringify({ email }) });
const user = await response.json();
set({ user }); // 2. Success
} catch (error) {
console.error(error);
} finally {
set({ isLoading: false }); // 3. Finish
}
},
}));6. Key Takeaways
Zustand is the go-to choice for client-side state management in React today, thanks to its:
- Simplicity: Learn it in under 5 minutes.
- Power: Full middleware support, DevTools integration, and robust TypeScript capabilities.
- Performance: Efficient selectors prevent unnecessary re-renders.