Zustand
Zustand State Management
Zustand State Management Basic Store import { create } from 'zustand'; // Simple store interface CounterStore { count: number; increment: () => void; decrement:…
Zustand State Management
Basic Store
import { create } from 'zustand';
// Simple store
interface CounterStore {
count: number;
increment: () => void;
decrement: () => void;
reset: () => void;
incrementBy: (amount: number) => void;
}
export const useCounterStore = create<CounterStore>((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
reset: () => set({ count: 0 }),
incrementBy: (amount) => set((state) => ({ count: state.count + amount })),
}));
// Usage in component
function Counter() {
const count = useCounterStore((state) => state.count);
const { increment, decrement, reset } = useCounterStore();
return (
<div>
<button onClick={decrement}>-</button>
<span>{count}</span>
<button onClick={increment}>+</button>
<button onClick={reset}>Reset</button>
</div>
);
}
// Select multiple — avoid selecting whole store (causes re-render on any change)
const { count, increment } = useCounterStore(
useShallow((state) => ({ count: state.count, increment: state.increment }))
);Advanced Patterns
import { create } from 'zustand';
import { devtools, persist, subscribeWithSelector } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';
// With Immer (mutable style updates) + devtools + persist
interface UserStore {
users: User[];
selectedId: string | null;
addUser: (user: User) => void;
removeUser: (id: string) => void;
updateUser: (id: string, data: Partial<User>) => void;
selectUser: (id: string) => void;
}
export const useUserStore = create<UserStore>()(
devtools(
persist(
immer((set) => ({
users: [],
selectedId: null,
addUser: (user) =>
set((state) => { state.users.push(user); }),
removeUser: (id) =>
set((state) => {
state.users = state.users.filter((u) => u.id !== id);
}),
updateUser: (id, data) =>
set((state) => {
const user = state.users.find((u) => u.id === id);
if (user) Object.assign(user, data);
}),
selectUser: (id) =>
set((state) => { state.selectedId = id; }),
})),
{
name: 'user-store', // localStorage key
partialize: (state) => ({ selectedId: state.selectedId }), // only persist selectedId
}
),
{ name: 'UserStore' } // devtools name
)
);
// Subscribe outside React (e.g., analytics, logging)
useUserStore.subscribe(
(state) => state.users.length,
(count) => analytics.track('users_count', { count }),
{ fireImmediately: true }
);
// Reset store (useful for logout)
const initialState = { users: [], selectedId: null };
useUserStore.setState(initialState);Async Actions & Slices
// Async actions — just use async functions
interface PostStore {
posts: Post[];
loading: boolean;
error: string | null;
fetchPosts: () => Promise<void>;
createPost: (data: CreatePostInput) => Promise<void>;
}
export const usePostStore = create<PostStore>((set) => ({
posts: [],
loading: false,
error: null,
fetchPosts: async () => {
set({ loading: true, error: null });
try {
const posts = await api.get('/posts');
set({ posts, loading: false });
} catch (err) {
set({ error: 'Failed to fetch posts', loading: false });
}
},
createPost: async (data) => {
const post = await api.post('/posts', data);
set((state) => ({ posts: [post, ...state.posts] }));
},
}));
// Slice pattern — combine multiple slices
const createCartSlice = (set, get) => ({
cart: [],
addToCart: (item) => set((s) => ({ cart: [...s.cart, item] })),
total: () => get().cart.reduce((sum, i) => sum + i.price, 0),
});
const createWishlistSlice = (set) => ({
wishlist: [],
addToWishlist: (item) => set((s) => ({ wishlist: [...s.wishlist, item] })),
});
export const useStore = create((...args) => ({
...createCartSlice(...args),
...createWishlistSlice(...args),
}));