Master React State Management: Redux, Context API, Zustand in Nextjs
MUKE JOHNBAPTIST

MUKE JOHNBAPTIST @mukecoder

Joined:
Feb 25, 2024

Master React State Management: Redux, Context API, Zustand in Nextjs

Publish Date: Jul 11 '25
0 0

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>;
}
Enter fullscreen mode Exit fullscreen mode

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

  1. 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' }
Enter fullscreen mode Exit fullscreen mode
  1. 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
  }
}
Enter fullscreen mode Exit fullscreen mode
  1. Create Context Use createContext to create the context object const CounterContext = createContext<CounterContextType | undefined>(undefined)
  2. 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>
  )
}
Enter fullscreen mode Exit fullscreen mode
  1. 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
}
Enter fullscreen mode Exit fullscreen mode
  1. Consumer Components Use the custom hook in components to access state
function Counter() {
  const { state, dispatch } = useCounter()
  // Use state and dispatch
}

Enter fullscreen mode Exit fullscreen mode

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>
  )
}
Enter fullscreen mode Exit fullscreen mode

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[]
}
Enter fullscreen mode Exit fullscreen mode

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
  }
})
Enter fullscreen mode Exit fullscreen mode

4 Configure Store
Set up the Redux store with configureStore

const store = configureStore({
  reducer: {
    counter: counterSlice.reducer
  }
})
Enter fullscreen mode Exit fullscreen mode

5 Setup Provider
Wrap your app with the Redux Provider

<Provider store={store}>
  <App />
</Provider>
Enter fullscreen mode Exit fullscreen mode

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>
  )
}
Enter fullscreen mode Exit fullscreen mode

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>
  )
}

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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' }
  )
)
Enter fullscreen mode Exit fullscreen mode

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>
  )
}
Enter fullscreen mode Exit fullscreen mode

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
}))
Enter fullscreen mode Exit fullscreen mode

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>
  )
}

Enter fullscreen mode Exit fullscreen mode

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.

Comments 0 total

    Add comment