Chapter 19: State Management Libraries
State Management Libraries — this is the chapter that finally lets you build large, complex, real-world React applications without going crazy with prop drilling, context hell, or scattered useState calls everywhere!
Today we’ll cover the three most popular and modern solutions in 2026:
- Redux Toolkit (the classic, battle-tested choice)
- Zustand (super simple & lightweight modern favorite)
- Jotai (atomic state — very elegant and flexible)
We’ll go through everything in detail — what they are, how they work, real examples, and most importantly: when to choose which one.
Let’s dive in like we’re sitting together in Mumbai with our laptops open and chai on the table! ☕
1. Introduction to Redux Toolkit (RTK)
Redux Toolkit is the official, modern way to use Redux. It removes 90% of the boilerplate that made classic Redux painful (no more switch statements, no more connect, no more manual action creators).
Key features:
- configureStore — one-line store setup
- createSlice — combines reducers + actions + action creators
- Built-in immutability with Immer
- DevTools support out of the box
- RTK Query — built-in data fetching & caching (like TanStack Query)
Real Example: Shopping Cart with Redux Toolkit
First, install:
|
0 1 2 3 4 5 6 |
npm install @reduxjs/toolkit react-redux |
Create store: src/store/index.ts
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import { configureStore } from '@reduxjs/toolkit'; import cartReducer from './cartSlice'; export const store = configureStore({ reducer: { cart: cartReducer } }); export type RootState = ReturnType<typeof store.getState>; export type AppDispatch = typeof store.dispatch; |
Create slice: src/store/cartSlice.ts
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
import { createSlice, PayloadAction } from '@reduxjs/toolkit'; interface CartItem { id: number; name: string; price: number; quantity: number; } interface CartState { items: CartItem[]; total: number; } const initialState: CartState = { items: [], total: 0 }; const cartSlice = createSlice({ name: 'cart', initialState, reducers: { addItem: (state, action: PayloadAction<CartItem>) => { const existing = state.items.find(item => item.id === action.payload.id); if (existing) { existing.quantity += 1; } else { state.items.push({ ...action.payload, quantity: 1 }); } state.total = state.items.reduce((sum, item) => sum + item.price * item.quantity, 0); }, removeItem: (state, action: PayloadAction<number>) => { state.items = state.items.filter(item => item.id !== action.payload); state.total = state.items.reduce((sum, item) => sum + item.price * item.quantity, 0); } } }); export const { addItem, removeItem } = cartSlice.actions; export default cartSlice.reducer; |
Wrap app in src/main.tsx:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 |
import { Provider } from 'react-redux'; import { store } from './store'; ReactDOM.createRoot(document.getElementById('root')!).render( <Provider store={store}> <App /> </Provider> ); |
Use in component:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
import { useSelector, useDispatch } from 'react-redux'; import { addItem, removeItem } from '../store/cartSlice'; import { RootState } from '../store'; function ProductCard({ id, name, price }: { id: number; name: string; price: number }) { const dispatch = useDispatch(); const cartItems = useSelector((state: RootState) => state.cart.items); const quantity = cartItems.find(item => item.id === id)?.quantity || 0; return ( <div className="p-6 border rounded-lg shadow-md"> <h3 className="text-xl font-bold">{name}</h3> <p className="text-lg">₹{price}</p> <div className="flex items-center gap-4 mt-4"> <button onClick={() => dispatch(removeItem(id))} disabled={quantity === 0} className="px-4 py-2 bg-red-500 text-white rounded disabled:opacity-50" > - </button> <span className="text-xl font-bold">{quantity}</span> <button onClick={() => dispatch(addItem({ id, name, price, quantity: 1 }))} className="px-4 py-2 bg-green-500 text-white rounded" > + </button> </div> </div> ); } |
2. Zustand (Modern, Tiny, Simple Alternative)
Zustand = minimalist global state — no boilerplate, no reducers, no actions.
Pros:
- Tiny (~1KB)
- Super simple API
- No context provider needed
- Full TypeScript support
- Selector-based subscriptions (only re-renders what you read)
Install:
|
0 1 2 3 4 5 6 |
npm install zustand |
Example: Cart store
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
// src/store/cartStore.ts import { create } from 'zustand'; interface CartItem { id: number; name: string; price: number; quantity: number; } interface CartState { items: CartItem[]; total: number; addItem: (item: Omit<CartItem, 'quantity'>) => void; removeItem: (id: number) => void; clearCart: () => void; } export const useCartStore = create<CartState>((set, get) => ({ items: [], total: 0, addItem: (newItem) => set(state => { const existing = state.items.find(i => i.id === newItem.id); let updatedItems; if (existing) { updatedItems = state.items.map(i => i.id === newItem.id ? { ...i, quantity: i.quantity + 1 } : i ); } else { updatedItems = [...state.items, { ...newItem, quantity: 1 }]; } const total = updatedItems.reduce((sum, i) => sum + i.price * i.quantity, 0); return { items: updatedItems, total }; }), removeItem: (id) => set(state => { const updatedItems = state.items.filter(i => i.id !== id); const total = updatedItems.reduce((sum, i) => sum + i.price * i.quantity, 0); return { items: updatedItems, total }; }), clearCart: () => set({ items: [], total: 0 }) })); |
Usage:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
import { useCartStore } from '../store/cartStore'; function CartSummary() { const { total, items, clearCart } = useCartStore(); return ( <div className="p-6 bg-gray-100 rounded-lg"> <h2 className="text-2xl font-bold">Cart Total: ₹{total}</h2> <p>{items.length} items</p> <button onClick={clearCart} className="mt-4 px-6 py-3 bg-red-500 text-white rounded-lg" > Clear Cart </button> </div> ); } |
No Provider needed — just import and use!
3. Jotai (Atomic State – Elegant & Flexible)
Jotai = atomic state management — you create tiny pieces of state (atoms) that can be composed.
Pros:
- Tiny (~3KB)
- No boilerplate
- Derived state (computed atoms)
- Great TypeScript support
- Works great with React Suspense
Install:
|
0 1 2 3 4 5 6 |
npm install jotai |
Example:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
// src/store/cartAtoms.ts import { atom } from 'jotai'; interface CartItem { id: number; name: string; price: number; quantity: number; } export const cartItemsAtom = atom<CartItem[]>([]); export const cartTotalAtom = atom(get => { const items = get(cartItemsAtom); return items.reduce((sum, item) => sum + item.price * item.quantity, 0); }); export const addToCartAtom = atom( null, (get, set, newItem: Omit<CartItem, 'quantity'>) => { const items = get(cartItemsAtom); const existing = items.find(i => i.id === newItem.id); if (existing) { set(cartItemsAtom, items.map(i => i.id === newItem.id ? { ...i, quantity: i.quantity + 1 } : i )); } else { set(cartItemsAtom, [...items, { ...newItem, quantity: 1 }]); } } ); |
Usage:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
import { useAtom } from 'jotai'; import { cartItemsAtom, cartTotalAtom, addToCartAtom } from '../store/cartAtoms'; function ProductCard({ id, name, price }: { id: number; name: string; price: number }) { const [, addToCart] = useAtom(addToCartAtom); return ( <div className="p-6 border rounded-lg"> <h3>{name}</h3> <p>₹{price}</p> <button onClick={() => addToCart({ id, name, price })} className="mt-4 px-4 py-2 bg-green-500 text-white rounded" > Add to Cart </button> </div> ); } function CartSummary() { const [items] = useAtom(cartItemsAtom); const [total] = useAtom(cartTotalAtom); return ( <div className="p-6 bg-gray-100 rounded-lg"> <h2>Cart Total: ₹{total}</h2> <p>{items.length} items</p> </div> ); } |
4. When to Choose Which (2026 Guide)
| Situation | Best Choice | Why / Recommendation |
|---|---|---|
| Small to medium app, simple global state | Zustand | Easiest, tiniest, no boilerplate — most developers love it in 2026 |
| Large app, complex state, team of 10+ | Redux Toolkit | Battle-tested, great DevTools, RTK Query for data fetching |
| Very atomic state, lots of derived/computed values | Jotai | Beautiful composition, Suspense support, tiny |
| You already have Redux or need RTK Query | Redux Toolkit | Best ecosystem + official React recommendation |
| Performance-sensitive + no Redux boilerplate | Zustand or Jotai | Both are extremely fast & lightweight |
| You want Context + atoms | Jotai | Basically Context on steroids |
My personal 2026 recommendation for most people:
- Start with Zustand — 95% of apps will be happy
- Move to Jotai if you love atomic style or need Suspense
- Use Redux Toolkit if the team is large, or you need RTK Query
Summary – Chapter 19 Key Takeaways
- Redux Toolkit → powerful, structured, great for big teams & complex apps
- Zustand → simplest & fastest global state (my favorite for most projects)
- Jotai → elegant atomic state, perfect for derived/computed values
- All three are modern, TypeScript-first, and tiny
- Start simple → only add complexity when needed
Mini Homework
- Pick one of the three
- Build a full shopping cart:
- Add/remove items
- Show total
- Persist to localStorage (bonus: use custom hook)
- Bonus: Add a “Clear Cart” button that resets everything
