State Management --- Zustand & React Query
⏱ 18 min read read
When You Need State Management:
Server Components + fetch caching = most data needs (no client lib
needed).
Zustand = lightweight global CLIENT state (cart, UI state,
preferences).
React Query = client-side data that needs auto-refetch, optimistic
updates.
Context = simple shared state across a few components.
Redux = legacy / large teams (usually overkill in Next.js 13+).
Zustand --- Minimal Global State:
// npm install zustand
import { create } from 'zustand';
interface CartStore {
items: CartItem[];
addItem: (item: CartItem) => void;
removeItem:(id: string) => void;
clear: () => void;
}
export const useCartStore = create<CartStore>((set) => ({
items: [],
addItem: (item) => set((s) => ({ items: [...s.items, item] })),
removeItem: (id) => set((s) => ({ items: s.items.filter(i => i.id
!== id) })),
clear: () => set({ items: [] }),
}));
// Usage in any Client Component
const { items, addItem } = useCartStore();
TanStack Query --- Server State on the Client:
// npm install @tanstack/react-query
import { useQuery, useMutation, useQueryClient } from
'@tanstack/react-query';
// Fetch + cache + refetch automatically
const { data, isLoading, error } = useQuery({
queryKey: ['students'],
queryFn: () => fetch('/api/students').then(r => r.json()),
staleTime: 60_000, // 1 minute before re-fetch
});
// Mutation with cache invalidation
const qc = useQueryClient();
const add = useMutation({
mutationFn: (data) => fetch('/api/students', { method: 'POST',
body: JSON.stringify(data) }),
onSuccess: () => qc.invalidateQueries({ queryKey: ['students']
}),
});
Server Components + fetch caching = most data needs (no client lib
needed).
Zustand = lightweight global CLIENT state (cart, UI state,
preferences).
React Query = client-side data that needs auto-refetch, optimistic
updates.
Context = simple shared state across a few components.
Redux = legacy / large teams (usually overkill in Next.js 13+).
Zustand --- Minimal Global State:
// npm install zustand
import { create } from 'zustand';
interface CartStore {
items: CartItem[];
addItem: (item: CartItem) => void;
removeItem:(id: string) => void;
clear: () => void;
}
export const useCartStore = create<CartStore>((set) => ({
items: [],
addItem: (item) => set((s) => ({ items: [...s.items, item] })),
removeItem: (id) => set((s) => ({ items: s.items.filter(i => i.id
!== id) })),
clear: () => set({ items: [] }),
}));
// Usage in any Client Component
const { items, addItem } = useCartStore();
TanStack Query --- Server State on the Client:
// npm install @tanstack/react-query
import { useQuery, useMutation, useQueryClient } from
'@tanstack/react-query';
// Fetch + cache + refetch automatically
const { data, isLoading, error } = useQuery({
queryKey: ['students'],
queryFn: () => fetch('/api/students').then(r => r.json()),
staleTime: 60_000, // 1 minute before re-fetch
});
// Mutation with cache invalidation
const qc = useQueryClient();
const add = useMutation({
mutationFn: (data) => fetch('/api/students', { method: 'POST',
body: JSON.stringify(data) }),
onSuccess: () => qc.invalidateQueries({ queryKey: ['students']
}),
});
Log in to track your progress and earn badges as you complete lessons.
Log In to Track Progress