LogoStarterkitpro
Features

Zustand

Using Zustand for simple global state management in client components.

Optional Feature

Zustand is not included by default to keep the core lean. Install it if you specifically need global client state.

Zustand: Simple Global State Management

Zustand is a small, fast, and scalable state-management solution using simplified flux principles. It's often compared to Redux but with much less boilerplate, feeling closer to using React's useState hook.

Often Not Required

In many Next.js applications you even might no need zustand. Server Components handle initial data very well, Server Actions handle mutations, and props can be passed down. Consider Zustand primarily when you need to share client-side state across multiple, unrelated components without excessive prop drilling.

Setup

Let's set up Zustand step by step:

1. Installation

Terminal
npm install zustand

2. Create a Task Store

Create a hook for managing shared application state. This example manages a list of tasks and a delete permission flag.

hooks/use-global-state.ts
import { create } from "zustand";
 
// ------ Exampe ------
 
// Define the task type
export interface Task {
  id: string;
  title: string;
  completed: boolean;
}
 
// Define the store state and actions
interface TaskState {
  tasks: Task[];
  canDelete: boolean;
  initializeTasks: (initialTasks: Task[]) => void;
  toggleTask: (id: string) => void;
  toggleDeletePermission: () => void;
}
 
// Create and export the store
export const useTaskStore = create<TaskState>((set) => ({
  // Initial state
  tasks: [], // Start with empty tasks
  canDelete: false, // Default permission
 
  // Action to set initial tasks (e.g., from props)
  initializeTasks: (initialTasks) => set({ tasks: initialTasks }),
 
  // Action to toggle task completion
  toggleTask: (id) =>
    set((state) => ({
      tasks: state.tasks.map((task) =>
        task.id === id ? { ...task, completed: !task.completed } : task
      ),
    })),
 
  // Action to toggle the delete permission
  toggleDeletePermission: () =>
    set((state) => ({ canDelete: !state.canDelete })),
}));
 
// ------ Create More stores here like that if needed  ------

Usage Example: Managing Tasks and Permissions

This component demonstrates initializing the store with data from props, updating task state, and managing a separate permission flag.

"use client";
 
import { useEffect } from "react";
import { useTaskStore, Task } from "@/hooks/use-global-state";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import { Label } from "@/components/ui/label";
 
interface TaskDisplayProps {
  initialTasks: Task[];
}
 
export function TaskDisplay({ initialTasks }: TaskDisplayProps) {
  // Get state and actions from the store
  const {
    tasks,
    canDelete,
    initializeTasks,
    toggleTask,
    toggleDeletePermission,
  } = useTaskStore();
 
  // Initialize tasks from props on component mount
  useEffect(() => {
    initializeTasks(initialTasks);
    // Cleanup function (optional): reset on unmount if needed
    // return () => initializeTasks([]);
  }, [initialTasks, initializeTasks]);
 
  return (
    <div className="space-y-4 p-4 border rounded-md">
      {/* Permission Toggle Section */}
      <div>
        <Button onClick={toggleDeletePermission}>
          {canDelete ? "Disable Deletion" : "Enable Deletion"}
        </Button>
        <p className="text-sm text-muted-foreground mt-1">
          Deletion is currently {canDelete ? "ENABLED" : "DISABLED"}.
        </p>
      </div>
 
      {/* Task List Section */}
      <div className="space-y-2">
        <h3 className="text-lg font-medium">Tasks</h3>
        {tasks.length === 0 ? (
          <p className="text-muted-foreground text-sm">No tasks loaded.</p>
        ) : (
          tasks.map((task) => (
            <div key={task.id} className="flex items-center gap-2">
              <Checkbox
                id={task.id}
                checked={task.completed}
                onCheckedChange={() => toggleTask(task.id)}
              />
              <Label
                htmlFor={task.id}
                className={
                  task.completed ? "line-through text-muted-foreground" : ""
                }
              >
                {task.title}
              </Label>
              {/* Example: Conditionally show delete button based on permission */}
              {canDelete && (
                <Button
                  variant="destructive"
                  size="sm"
                  onClick={() => alert(`(Demo) Deleting task: ${task.title}`)}
                  className="ml-auto"
                >
                  Delete
                </Button>
              )}
            </div>
          ))
        )}
      </div>
    </div>
  );
}

How It Works

  1. Store Creation: A central store (useTaskStore defined in hooks/use-global-state.ts) holds shared state (tasks, canDelete) and actions (initializeTasks, toggleTask, toggleDeletePermission).
  2. Initialization: The TaskDisplay component receives initialTasks via props and calls initializeTasks inside a useEffect hook (running once on mount) to populate the store's task list.
  3. State Updates: User interactions (clicking checkboxes or the permission button) call actions (toggleTask, toggleDeletePermission) which update the state in the central store.
  4. Reactivity: Zustand ensures that TaskDisplay (and any other component using useTaskStore) re-renders automatically when the parts of the state it subscribes to (tasks, canDelete) change.

On this page