Introduction
State management is a crucial aspect of modern React applications, especially when building complex Next.js applications.
When building React applications, we typically pass data from parent to child components via props. It would be easy if we had few layers of components. However, some components are deeply nested.
Things become complex as you introduce more components with several nesting levels. Keeping track of state and props can become cumbersome.
The Global state management solutions like Context API, Redux or Zustand provides functionality for passing data from a parent component to its descendants without prop drilling.
This guide covers three popular state management solutions:
- Context API: React's built-in state management solution
- Redux Toolkit: The modern Redux approach with less boilerplate
- Zustand: A lightweight, flexible state management library
We'll implement a counter project using each approach, then build a comprehensive cart system with Zustand to demonstrate real-world usage.
What is prop drilling?
React is a declarative, component-based UI framework. You will almost always need to compose two or more components when building UIs. In React, a parent component can primarily share data with its children via props.
However it becomes more complex if your app has several nesting levels and you have to pass data from the topmost to the innermost component.
As an example, assume ComponentA renders ComponentB and ComponentB renders ComponentC. If you want the topmost component to share data with the innermost component, you need to pass props from ComponentA to ComponentB and then finally from ComponentB to ComponentC.
function ComponentA() {
const [counter, setCounter] = useState(0);
return <ComponentB counter={counter} />;
}
function ComponentB({ counter }) {
return <ComponentC counter={counter} />;
}
function ComponentC({ counter }) {
return <p>{counter}</p>;
}
In React, prop drilling refers to passing data via props through multiple layers of nested components, as in the above example. Props are useful for basic data sharing between a component and its children.
However, drilling props through several layers of nesting can make your components less readable, difficult to maintain, and reuse. You can solve this prop drilling problem using the React context API.
Complete Step-by-Step Implementation Guide
Context API Implementation
Context API
React's built-in solution for sharing state across components without prop drilling.
Features:
• Built into React
• No external dependencies
• Perfect for simple state
• Easy to implement
pnpm create next-app state-management-guide
cd state-management-guide
Summary of the Implementation Steps
- Define State & Actions Create TypeScript interfaces for state shape and action types
interface CounterState {
count: number
history: number[]
}
type CounterAction =
| { type: 'INCREMENT' }
| { type: 'DECREMENT' }
| { type: 'RESET' }
- Create Reducer Implement reducer function to handle state updates
function counterReducer(state: CounterState, action: CounterAction): CounterState {
switch (action.type) {
case 'INCREMENT':
return {
count: state.count + 1,
}
// ... other cases
}
}
- Create Context
Use createContext to create the context object
const CounterContext = createContext<CounterContextType | undefined>(undefined) - Provider Component Create provider component using useReducer hook
function CounterProvider({ children }: { children: ReactNode }) {
const [state, dispatch] = useReducer(counterReducer, initialState)
return (
<CounterContext.Provider value={{ state, dispatch }}>
{children}
</CounterContext.Provider>
)
}
- Custom Hook Create useCounter hook for consuming context safely
function useCounter() {
const context = useContext(CounterContext)
if (context === undefined) {
throw new Error('useCounter must be used within a CounterProvider')
}
return context
}
- Consumer Components Use the custom hook in components to access state
function Counter() {
const { state, dispatch } = useCounter()
// Use state and dispatch
}
Full implementation
"use client"
import type React from "react"
import { createContext, useContext, useReducer, type ReactNode } from "react"
import Link from "next/link"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { ArrowLeft, Plus, Minus, RotateCcw, Layers } from "lucide-react"
// Step 1: Define the state shape and actions
interface CounterState {
count: number
}
type CounterAction = { type: "INCREMENT" } | { type: "DECREMENT" } | { type: "RESET" }
// Step 2: Create the reducer function
function counterReducer(state: CounterState, action: CounterAction): CounterState {
switch (action.type) {
case "INCREMENT":
return {
count: state.count + 1,
}
case "DECREMENT":
return {
count: state.count - 1,
}
case "RESET":
return {
count: 0,
}
default:
return state
}
}
// Step 3: Create Context
interface CounterContextType {
state: CounterState
dispatch: React.Dispatch<CounterAction>
}
const CounterContext = createContext<CounterContextType | undefined>(undefined)
// Step 4: Create Provider Component
function CounterProvider({ children }: { children: ReactNode }) {
const [state, dispatch] = useReducer(counterReducer, {
count: 0,
history: [0],
})
return <CounterContext.Provider value={{ state, dispatch }}>{children}</CounterContext.Provider>
}
// Step 5: Create custom hook for consuming context
function useCounter() {
const context = useContext(CounterContext)
if (context === undefined) {
throw new Error("useCounter must be used within a CounterProvider")
}
return context
}
// Step 6: Create Counter Component
function Counter() {
const { state, dispatch } = useCounter()
return (
<Card className="w-full max-w-md mx-auto">
<CardHeader className="text-center">
<div className="w-16 h-16 bg-blue-100 rounded-full flex items-center justify-center mx-auto mb-4">
<Layers className="w-8 h-8 text-blue-600" />
</div>
<CardTitle className="text-3xl font-bold">Context API Counter</CardTitle>
<CardDescription>Using React's built-in Context API with useReducer</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div className="text-center">
<div className="text-6xl font-bold text-blue-600 mb-2">{state.count}</div>
<Badge variant="secondary">Current Count</Badge>
</div>
<div className="flex gap-3 justify-center">
<Button onClick={() => dispatch({ type: "DECREMENT" })} variant="outline" size="lg" className="flex-1">
<Minus className="w-4 h-4 mr-2" />
Decrement
</Button>
<Button onClick={() => dispatch({ type: "INCREMENT" })} size="lg" className="flex-1">
<Plus className="w-4 h-4 mr-2" />
Increment
</Button>
</div>
<Button onClick={() => dispatch({ type: "RESET" })} variant="destructive" className="w-full">
<RotateCcw className="w-4 h-4 mr-2" />
Reset Counter
</Button>
</CardContent>
</Card>
)
}
// Step 7: Create the main page component
export default function ContextAPIPage() {
return (
<CounterProvider>
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 p-8">
<div className="max-w-4xl mx-auto">
<div className="mb-8">
<Link href="/">
<Button variant="ghost" className="mb-4">
<ArrowLeft className="w-4 h-4 mr-2" />
Back to Home
</Button>
</Link>
<h1 className="text-3xl font-bold text-gray-900 mb-2">Context API Implementation</h1>
<p className="text-gray-600">React's built-in solution for global state management</p>
</div>
<div className="grid lg:grid-cols-2 gap-8">
<Counter />
<Card>
<CardHeader>
<CardTitle>Implementation Steps</CardTitle>
<CardDescription>How this Context API counter was built</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-3">
<div className="flex items-start gap-3">
<Badge className="mt-1">1</Badge>
<div>
<h4 className="font-semibold">Define State & Actions</h4>
<p className="text-sm text-gray-600">
Create TypeScript interfaces for state shape and action types
</p>
</div>
</div>
<div className="flex items-start gap-3">
<Badge className="mt-1">2</Badge>
<div>
<h4 className="font-semibold">Create Reducer</h4>
<p className="text-sm text-gray-600">Implement reducer function to handle state updates</p>
</div>
</div>
<div className="flex items-start gap-3">
<Badge className="mt-1">3</Badge>
<div>
<h4 className="font-semibold">Create Context</h4>
<p className="text-sm text-gray-600">Use createContext to create the context object</p>
</div>
</div>
<div className="flex items-start gap-3">
<Badge className="mt-1">4</Badge>
<div>
<h4 className="font-semibold">Provider Component</h4>
<p className="text-sm text-gray-600">Create provider component using useReducer hook</p>
</div>
</div>
<div className="flex items-start gap-3">
<Badge className="mt-1">5</Badge>
<div>
<h4 className="font-semibold">Custom Hook</h4>
<p className="text-sm text-gray-600">Create useCounter hook for consuming context safely</p>
</div>
</div>
<div className="flex items-start gap-3">
<Badge className="mt-1">6</Badge>
<div>
<h4 className="font-semibold">Consumer Components</h4>
<p className="text-sm text-gray-600">Use the custom hook in components to access state</p>
</div>
</div>
</div>
<div className="border-t pt-4">
<h4 className="font-semibold mb-2">Pros & Cons</h4>
<div className="space-y-2 text-sm">
<div>
<span className="text-green-600 font-medium">Pros:</span>
<span className="text-gray-600"> Built-in, no dependencies, simple setup</span>
</div>
<div>
<span className="text-red-600 font-medium">Cons:</span>
<span className="text-gray-600"> Can cause re-renders, limited debugging tools</span>
</div>
</div>
</div>
</CardContent>
</Card>
</div>
</div>
</div>
</CounterProvider>
)
}
Pros & Cons
Pros: Built-in, no dependencies, simple setup
Cons: Can cause re-renders, limited debugging tools
Redux
Predictable state container with time-travel debugging and middleware support.
Features:
• Predictable state updates
• Time-travel debugging
• Middleware ecosystem
• Great for complex apps
Implementation Steps
1 Install Dependencies
pnpm install @reduxjs/toolkit react-redux
2 Define State Interface
Create TypeScript interfaces for your state
interface CounterState {
count: number
history: number[]
}
3 Create Slice
Use createSlice to define reducers and actions
const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
increment: (state) => {
state.count += 1
state.history.push(state.count)
},
// ... other reducers
}
})
4 Configure Store
Set up the Redux store with configureStore
const store = configureStore({
reducer: {
counter: counterSlice.reducer
}
})
5 Setup Provider
Wrap your app with the Redux Provider
<Provider store={store}>
<App />
</Provider>
6 Use Redux Hooks
Use useSelector and useDispatch in components
function Counter() {
const { count, history } = useSelector((state: RootState) => state.counter)
const dispatch = useDispatch<AppDispatch>()
return (
<button onClick={() => dispatch(increment())}>
Count: {count}
</button>
)
}
Full Implementation
"use client"
import { Provider, useSelector, useDispatch } from "react-redux"
import { configureStore, createSlice, type PayloadAction } from "@reduxjs/toolkit"
import Link from "next/link"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { ArrowLeft, Plus, Minus, RotateCcw, Database } from "lucide-react"
// Step 1: Define the state interface
interface CounterState {
count: number
history: number[]
}
// Step 2: Create initial state
const initialState: CounterState = {
count: 0,
history: [0],
}
// Step 3: Create Redux slice using Redux Toolkit
const counterSlice = createSlice({
name: "counter",
initialState,
reducers: {
increment: (state) => {
state.count += 1
state.history.push(state.count)
},
decrement: (state) => {
state.count -= 1
state.history.push(state.count)
},
reset: (state) => {
state.count = 0
state.history = [0]
},
incrementByAmount: (state, action: PayloadAction<number>) => {
state.count += action.payload
state.history.push(state.count)
},
},
})
// Step 4: Export actions
export const { increment, decrement, reset, incrementByAmount } = counterSlice.actions
// Step 5: Configure store
const store = configureStore({
reducer: {
counter: counterSlice.reducer,
},
})
// Step 6: Define RootState type
type RootState = ReturnType<typeof store.getState>
type AppDispatch = typeof store.dispatch
// Step 7: Create Counter Component
function Counter() {
// Step 8: Use Redux hooks
const { count, history } = useSelector((state: RootState) => state.counter)
const dispatch = useDispatch<AppDispatch>()
return (
<Card className="w-full max-w-md mx-auto">
<CardHeader className="text-center">
<div className="w-16 h-16 bg-purple-100 rounded-full flex items-center justify-center mx-auto mb-4">
<Database className="w-8 h-8 text-purple-600" />
</div>
<CardTitle className="text-3xl font-bold">Redux Counter</CardTitle>
<CardDescription>Using Redux Toolkit for predictable state management</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div className="text-center">
<div className="text-6xl font-bold text-purple-600 mb-2">{count}</div>
<Badge variant="secondary">Current Count</Badge>
</div>
<div className="flex gap-3 justify-center">
<Button onClick={() => dispatch(decrement())} variant="outline" size="lg" className="flex-1">
<Minus className="w-4 h-4 mr-2" />
Decrement
</Button>
<Button onClick={() => dispatch(increment())} size="lg" className="flex-1">
<Plus className="w-4 h-4 mr-2" />
Increment
</Button>
</div>
<div className="flex gap-2">
<Button onClick={() => dispatch(incrementByAmount(5))} variant="secondary" className="flex-1">
+5
</Button>
<Button onClick={() => dispatch(incrementByAmount(10))} variant="secondary" className="flex-1">
+10
</Button>
</div>
<Button onClick={() => dispatch(reset())} variant="destructive" className="w-full">
<RotateCcw className="w-4 h-4 mr-2" />
Reset Counter
</Button>
<div className="border-t pt-4">
<h3 className="font-semibold mb-2">History</h3>
<div className="flex flex-wrap gap-1">
{history.slice(-10).map((value, index) => (
<Badge key={index} variant="outline" className="text-xs">
{value}
</Badge>
))}
</div>
</div>
</CardContent>
</Card>
)
}
// Step 9: Create the main page component with Provider
export default function ReduxPage() {
return (
<Provider store={store}>
<div className="min-h-screen bg-gradient-to-br from-purple-50 to-pink-100 p-8">
<div className="max-w-4xl mx-auto">
<div className="mb-8">
<Link href="/">
<Button variant="ghost" className="mb-4">
<ArrowLeft className="w-4 h-4 mr-2" />
Back to Home
</Button>
</Link>
<h1 className="text-3xl font-bold text-gray-900 mb-2">Redux Implementation</h1>
<p className="text-gray-600">Predictable state container with Redux Toolkit</p>
</div>
<div className="grid lg:grid-cols-2 gap-8">
<Counter />
<Card>
<CardHeader>
<CardTitle>Implementation Steps</CardTitle>
<CardDescription>How this Redux counter was built</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-3">
<div className="flex items-start gap-3">
<Badge className="mt-1">1</Badge>
<div>
<h4 className="font-semibold">Install Dependencies</h4>
<p className="text-sm text-gray-600">npm install @reduxjs/toolkit react-redux</p>
</div>
</div>
<div className="flex items-start gap-3">
<Badge className="mt-1">2</Badge>
<div>
<h4 className="font-semibold">Define State Interface</h4>
<p className="text-sm text-gray-600">Create TypeScript interfaces for your state</p>
</div>
</div>
<div className="flex items-start gap-3">
<Badge className="mt-1">3</Badge>
<div>
<h4 className="font-semibold">Create Slice</h4>
<p className="text-sm text-gray-600">Use createSlice to define reducers and actions</p>
</div>
</div>
<div className="flex items-start gap-3">
<Badge className="mt-1">4</Badge>
<div>
<h4 className="font-semibold">Configure Store</h4>
<p className="text-sm text-gray-600">Set up the Redux store with configureStore</p>
</div>
</div>
<div className="flex items-start gap-3">
<Badge className="mt-1">5</Badge>
<div>
<h4 className="font-semibold">Setup Provider</h4>
<p className="text-sm text-gray-600">Wrap your app with the Redux Provider</p>
</div>
</div>
<div className="flex items-start gap-3">
<Badge className="mt-1">6</Badge>
<div>
<h4 className="font-semibold">Use Hooks</h4>
<p className="text-sm text-gray-600">Use useSelector and useDispatch in components</p>
</div>
</div>
</div>
<div className="border-t pt-4">
<h4 className="font-semibold mb-2">Key Features</h4>
<div className="space-y-2 text-sm">
<div>
• <span className="font-medium">Immutable Updates:</span> Redux Toolkit uses Immer
</div>
<div>
• <span className="font-medium">DevTools:</span> Built-in Redux DevTools support
</div>
<div>
• <span className="font-medium">Middleware:</span> Thunk middleware included
</div>
<div>
• <span className="font-medium">TypeScript:</span> Excellent TypeScript support
</div>
</div>
</div>
<div className="border-t pt-4">
<h4 className="font-semibold mb-2">Pros & Cons</h4>
<div className="space-y-2 text-sm">
<div>
<span className="text-green-600 font-medium">Pros:</span>
<span className="text-gray-600"> Predictable, great debugging, middleware</span>
</div>
<div>
<span className="text-red-600 font-medium">Cons:</span>
<span className="text-gray-600"> More boilerplate, learning curve</span>
</div>
</div>
</div>
</CardContent>
</Card>
</div>
</div>
</div>
</Provider>
)
}
Key Features
• Immutable Updates: Redux Toolkit uses Immer
• DevTools: Built-in Redux DevTools support
• Middleware: Thunk middleware included
• TypeScript: Excellent TypeScript support
Pros & Cons
Pros: Predictable, great debugging, middleware
Cons: More boilerplate, learning curve
Zustand
Lightweight, flexible state management with minimal boilerplate code.
Features:
• Minimal boilerplate
• TypeScript friendly
• No providers needed
• Modern and flexible
Implementation Steps
1 Install Zustand
pnpm install zustand
2 Define Interfaces
Create TypeScript interfaces for state and actions
interface CounterState {
count: number
}
interface CounterActions {
increment: () => void
decrement: () => void
reset: () => void
}
type CounterStore = CounterState & CounterActions
3 Create Store
Use create() function to define your store
const useCounterStore = create<CounterStore>()(
devtools(
persist(
(set, get) => ({
count: 0,
increment: () => set((state) => ({
count: state.count + 1,
})),
// ... other actions
}),
{ name: 'counter-storage' }
),
{ name: 'counter-store' }
)
)
4 Add Middleware
Optional: Add devtools and persist middleware
5 Use in Components
Call the hook directly in any component
function Counter() {
const { count, increment, decrement } = useCounterStore()
return (
<div>
<button onClick={increment}>+</button>
<span>{count}</span>
<button onClick={decrement}>-</button>
</div>
)
}
6 Selective Subscriptions
Subscribe to specific parts of state for optimization
// Only subscribe to count
const count = useCounterStore((state) => state.count)
// Subscribe to multiple specific fields
const { count, increment } = useCounterStore((state) => ({
count: state.count,
increment: state.increment
}))
Full Implementation
"use client"
import { create } from "zustand"
import { devtools, persist } from "zustand/middleware"
import Link from "next/link"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { ArrowLeft, Plus, Minus, RotateCcw, Zap } from "lucide-react"
// Step 1: Define the state interface
interface CounterState {
count: number
history: number[]
}
// Step 2: Define the actions interface
interface CounterActions {
increment: () => void
decrement: () => void
reset: () => void
incrementByAmount: (amount: number) => void
}
// Step 3: Combine state and actions
type CounterStore = CounterState & CounterActions
// Step 4: Create the Zustand store
const useCounterStore = create<CounterStore>()(
devtools(
persist(
(set, get) => ({
// Initial state
count: 0,
history: [0],
// Actions
increment: () =>
set(
(state) => {
const newCount = state.count + 1
return {
count: newCount,
history: [...state.history, newCount],
}
},
false,
"increment",
),
decrement: () =>
set(
(state) => {
const newCount = state.count - 1
return {
count: newCount,
history: [...state.history, newCount],
}
},
false,
"decrement",
),
reset: () =>
set(
() => ({
count: 0,
history: [0],
}),
false,
"reset",
),
incrementByAmount: (amount: number) =>
set(
(state) => {
const newCount = state.count + amount
return {
count: newCount,
history: [...state.history, newCount],
}
},
false,
"incrementByAmount",
),
}),
{
name: "counter-storage", // unique name for localStorage
partialize: (state) => ({ count: state.count }), // only persist count
},
),
{
name: "counter-store", // name for devtools
},
),
)
// Step 5: Create Counter Component
function Counter() {
// Step 6: Use the store - can select specific parts
const { count, history, increment, decrement, reset, incrementByAmount } = useCounterStore()
return (
<Card className="w-full max-w-md mx-auto">
<CardHeader className="text-center">
<div className="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-4">
<Zap className="w-8 h-8 text-green-600" />
</div>
<CardTitle className="text-3xl font-bold">Zustand Counter</CardTitle>
<CardDescription>Lightweight state management with minimal boilerplate</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div className="text-center">
<div className="text-6xl font-bold text-green-600 mb-2">{count}</div>
<Badge variant="secondary">Current Count</Badge>
</div>
<div className="flex gap-3 justify-center">
<Button onClick={decrement} variant="outline" size="lg" className="flex-1 bg-transparent">
<Minus className="w-4 h-4 mr-2" />
Decrement
</Button>
<Button onClick={increment} size="lg" className="flex-1">
<Plus className="w-4 h-4 mr-2" />
Increment
</Button>
</div>
<div className="flex gap-2">
<Button onClick={() => incrementByAmount(5)} variant="secondary" className="flex-1">
+5
</Button>
<Button onClick={() => incrementByAmount(10)} variant="secondary" className="flex-1">
+10
</Button>
<Button onClick={() => incrementByAmount(-3)} variant="secondary" className="flex-1">
-3
</Button>
</div>
<Button onClick={reset} variant="destructive" className="w-full">
<RotateCcw className="w-4 h-4 mr-2" />
Reset Counter
</Button>
<div className="border-t pt-4">
<h3 className="font-semibold mb-2">History</h3>
<div className="flex flex-wrap gap-1">
{history.slice(-10).map((value, index) => (
<Badge key={index} variant="outline" className="text-xs">
{value}
</Badge>
))}
</div>
</div>
</CardContent>
</Card>
)
}
// Step 7: Create a component that demonstrates selective subscriptions
function CounterStats() {
// Only subscribe to count, not history
const count = useCounterStore((state) => state.count)
return (
<Card>
<CardHeader>
<CardTitle className="text-lg">Counter Stats</CardTitle>
<CardDescription>This component only re-renders when count changes</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-2">
<div className="flex justify-between">
<span>Current Value:</span>
<Badge>{count}</Badge>
</div>
<div className="flex justify-between">
<span>Is Positive:</span>
<Badge variant={count > 0 ? "default" : "secondary"}>{count > 0 ? "Yes" : "No"}</Badge>
</div>
<div className="flex justify-between">
<span>Is Even:</span>
<Badge variant={count % 2 === 0 ? "default" : "secondary"}>{count % 2 === 0 ? "Yes" : "No"}</Badge>
</div>
</div>
</CardContent>
</Card>
)
}
// Step 8: Main page component (no provider needed!)
export default function ZustandPage() {
return (
<div className="min-h-screen bg-gradient-to-br from-green-50 to-emerald-100 p-8">
<div className="max-w-4xl mx-auto">
<div className="mb-8">
<Link href="/">
<Button variant="ghost" className="mb-4">
<ArrowLeft className="w-4 h-4 mr-2" />
Back to Home
</Button>
</Link>
<h1 className="text-3xl font-bold text-gray-900 mb-2">Zustand Implementation</h1>
<p className="text-gray-600">Modern, lightweight state management solution</p>
</div>
<div className="grid lg:grid-cols-2 gap-8">
<div className="space-y-6">
<Counter />
<CounterStats />
</div>
<Card>
<CardHeader>
<CardTitle>Implementation Steps</CardTitle>
<CardDescription>How this Zustand counter was built</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-3">
<div className="flex items-start gap-3">
<Badge className="mt-1">1</Badge>
<div>
<h4 className="font-semibold">Install Zustand</h4>
<p className="text-sm text-gray-600">npm install zustand</p>
</div>
</div>
<div className="flex items-start gap-3">
<Badge className="mt-1">2</Badge>
<div>
<h4 className="font-semibold">Define Interfaces</h4>
<p className="text-sm text-gray-600">Create TypeScript interfaces for state and actions</p>
</div>
</div>
<div className="flex items-start gap-3">
<Badge className="mt-1">3</Badge>
<div>
<h4 className="font-semibold">Create Store</h4>
<p className="text-sm text-gray-600">Use create() function to define your store</p>
</div>
</div>
<div className="flex items-start gap-3">
<Badge className="mt-1">4</Badge>
<div>
<h4 className="font-semibold">Add Middleware</h4>
<p className="text-sm text-gray-600">Optional: Add devtools and persist middleware</p>
</div>
</div>
<div className="flex items-start gap-3">
<Badge className="mt-1">5</Badge>
<div>
<h4 className="font-semibold">Use in Components</h4>
<p className="text-sm text-gray-600">Call the hook directly in any component</p>
</div>
</div>
<div className="flex items-start gap-3">
<Badge className="mt-1">6</Badge>
<div>
<h4 className="font-semibold">Selective Subscriptions</h4>
<p className="text-sm text-gray-600">Subscribe to specific parts of state for optimization</p>
</div>
</div>
</div>
<div className="border-t pt-4">
<h4 className="font-semibold mb-2">Key Features</h4>
<div className="space-y-2 text-sm">
<div>
• <span className="font-medium">No Providers:</span> Use anywhere without wrapping
</div>
<div>
• <span className="font-medium">Selective Subscriptions:</span> Components only re-render when
needed
</div>
<div>
• <span className="font-medium">Middleware:</span> DevTools, persistence, and more
</div>
<div>
• <span className="font-medium">TypeScript:</span> Excellent TypeScript support
</div>
<div>
• <span className="font-medium">Small Bundle:</span> Only ~2.5kb gzipped
</div>
</div>
</div>
<div className="border-t pt-4">
<h4 className="font-semibold mb-2">Pros & Cons</h4>
<div className="space-y-2 text-sm">
<div>
<span className="text-green-600 font-medium">Pros:</span>
<span className="text-gray-600"> Minimal boilerplate, no providers, great performance</span>
</div>
<div>
<span className="text-red-600 font-medium">Cons:</span>
<span className="text-gray-600"> Newer ecosystem, less middleware options</span>
</div>
</div>
</div>
</CardContent>
</Card>
</div>
</div>
</div>
)
}
Key Features
• No Providers: Use anywhere without wrapping
• Selective Subscriptions: Components only re-render when needed
• Middleware: DevTools, persistence, and more
• TypeScript: Excellent TypeScript support
• Small Bundle: Only ~2.5kb gzipped
Pros & Cons
Pros: Minimal boilerplate, no providers, great performance
Cons: Newer ecosystem, less middleware options
Comparison Summary
| Feature | Context API | Redux | Zustand |
|---|---|---|---|
| Bundle Size | 0kb (built-in) | ~11kb | ~2.5kb |
| Boilerplate | Medium | High | Low |
| Learning Curve | Easy | Steep | Easy |
| DevTools | Limited | Excellent | Good |
| TypeScript | Good | Excellent | Excellent |
| Performance | Can cause re-renders | Optimized | Optimized |
| Middleware | None | Rich ecosystem | Growing |
| Provider Required | Yes | Yes | No |
When to Use Each
- Context API: Small to medium apps, simple state, no external dependencies needed
- Redux: Large applications, complex state logic, need for time-travel debugging
- Zustand: Modern apps, want minimal boilerplate, need good performance with simplicity
This implementation provides a complete comparison of all three state management solutions with working examples and detailed explanations for your article.

