π§ What is Zustand?
Zustand (German for “state”) is a modern, minimalistic, and scalable state management library for React applications. Built by the creators of Jotai and React Three Fiber, Zustand solves the complexity of state sharing across components without context providers, reducers, or the boilerplate of Redux.
β Why Choose Zustand?
- No Provider Required: Zustand doesnβt need a
<Provider>component like Redux or Context API. - Hook-Based API: Simply use React hooks to read and update state.
- Slices of State: Subscribe to only the state slices you care about.
- Middleware Support: Add devtools, persistence, or logging easily.
- SSR Friendly: Built to work with frameworks like Next.js.
π Zustandβs Core Flow
Zustand works through:
- A global store created using the
createfunction. - React hooks that allow components to read/update the store.
- Selective subscriptions: Components re-render only if their slice changes.
Hereβs a simple illustration:
import { create } from 'zustand';
const useCounter = create((set) => ({
count: 0,
increase: () => set((state) => ({ count: state.count + 1 }))
}));
In your component:
const { count, increase } = useCounter();
No Provider. No Boilerplate. Super intuitive.
π‘ How Zustand Works with Next.js and JSON Server
- Zustand acts as a shared state container.
- JSON Server acts as a fake backend.
- All API interactions happen inside Zustand and are shared with components.
- The entire CRUD logic is centralized in
store/userStore.js.
This keeps components clean and logic encapsulated.
π Step 1: Set Up the Project
Create your Next.js app:
zustand-crud-app/ βββ db.json # Fake database for JSON Server βββ package.json # npm scripts (dev + server) βββ pages/ β βββ index.js # Main page with all components βββ components/ β βββ Header.js # Shows dynamic cart count β βββ ProductList.js # Product catalog with "Add to Cart" β βββ UserForm.js # Add/Edit user form β βββ UserList.js # User table and delete button βββ store/ β βββ userStore.js # Zustand store for users β βββ shopStore.js # Zustand store for products + cart βββ public/ β βββ favicon.ico (default) # Static assets (optional) βββ styles/ β βββ globals.css (optional) # Global CSS (if using Tailwind
npx create-next-app zustand-crud-app cd zustand-crud-app npm install zustand json-server --save-dev
π Project Structure
zustand-crud-app/
βββ db.json # JSON Server Fake DB
βββ store/userStore.js # Zustand store for global state
βββ components/UserForm.js # Add/Edit form
βββ components/UserList.js # User listing and delete
βββ pages/index.js # Main page
βββ package.json # Scripts for server + dev
βοΈ Step 2: Configure JSON Server
1. Create a db.json file in the root:
{
"users": [
{
"id": 1,
"firstName": "John",
"lastName": "Doe",
"email": "john@example.com",
"password": "123456"
}
]
}
2. Add this script to package.json:
"scripts": {
"dev": "next dev",
"server": "json-server --watch db.json --port 5000"
}
3. Start JSON Server:
npm run server
Visit http://localhost:5000/users to access the API.
π§ Step 3: Create the Zustand Store
File: store/userStore.js
import { create } from 'zustand';
export interface User {
id?: number;
firstName: string;
lastName: string;
email: string;
password: string;
}
interface UserStore {
users: User[];
fetchUsers: () => Promise<void>;
addUser: (user: User) => Promise<void>;
updateUser: (id: number, updatedUser: User) => Promise<void>;
deleteUser: (id: number) => Promise<void>;
}
const API = 'http://localhost:5000/users';
const useUserStore = create<UserStore>((set) => ({
users: [],
fetchUsers: async () => {
const res = await fetch(API);
const data = await res.json();
set({ users: data });
},
addUser: async (user) => {
const res = await fetch(API, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(user),
});
const newUser = await res.json();
set((state) => ({ users: [...state.users, newUser] }));
},
updateUser: async (id, updatedUser) => {
await fetch(`${API}/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updatedUser),
});
set((state) => ({
users: state.users.map((u) =>
u.id === id ? { ...u, ...updatedUser } : u
),
}));
},
deleteUser: async (id) => {
await fetch(`${API}/${id}`, { method: 'DELETE' });
set((state) => ({
users: state.users.filter((u) => u.id !== id),
}));
},
}));
export default useUserStore;
π Step 4: Create User Form Component
File: components/UserForm.js
import { useState, useEffect } from 'react';
import useUserStore from '../store/userStore';
export default function UserForm({ editData, setEditData }) {
const { addUser, updateUser } = useUserStore();
const [form, setForm] = useState({ firstName: '', lastName: '', email: '', password: '' });
useEffect(() => {
if (editData) setForm(editData);
else setForm({ firstName: '', lastName: '', email: '', password: '' });
}, [editData]);
const handleChange = (e) => {
setForm({ ...form, [e.target.name]: e.target.value });
};
const handleSubmit = (e) => {
e.preventDefault();
if (editData) {
updateUser(editData.id, form);
setEditData(null);
} else {
addUser(form);
}
setForm({ firstName: '', lastName: '', email: '', password: '' });
};
return (
<form onSubmit={handleSubmit}>
<input name="firstName" value={form.firstName} onChange={handleChange} />
<input name="lastName" value={form.lastName} onChange={handleChange} />
<input name="email" type="email" value={form.email} onChange={handleChange} />
<input name="password" type="password" value={form.password} onChange={handleChange} />
<button type="submit">{editData ? 'Update' : 'Add'} User</button>
</form>
);
}
π Step 5: Create User List Component
File: components/UserList.js
import { useEffect } from 'react';
import useUserStore from '../store/userStore';
export default function UserList({ setEditData }) {
const { users, fetchUsers, deleteUser } = useUserStore();
useEffect(() => {
fetchUsers();
}, []);
return (
<div>
<h2>User List</h2>
{users.length === 0 && <p>No users found.</p>}
<ul>
{users.map((user) => (
<li key={user.id}>
<div>{user.firstName} {user.lastName} - {user.email}</div>
<button onClick={() => setEditData(user)}>Edit</button>
<button onClick={() => deleteUser(user.id)}>Delete</button>
</li>
))}
</ul>
</div>
);
}
π Step 6: Build the Main Page
File: pages/index.js
import { useState } from 'react';
import UserForm from '../components/UserForm';
import UserList from '../components/UserList';
export default function Home() {
const [editData, setEditData] = useState(null);
return (
<main style={{ maxWidth: '600px', margin: '2rem auto' }}>
<h1>User CRUD with Zustand + JSON Server</h1>
<UserForm editData={editData} setEditData={setEditData} />
<UserList setEditData={setEditData} />
</main>
);
}
π§ͺ Run Everything
Terminal 1: Start the Next.js app
npm run dev
Terminal 2: Start the JSON server
npm run server
Visit http://localhost:3000 to try your full-featured CRUD app.
π§© Add To Cart Example
-
Add
productstodb.json(mock product catalog). -
Create Zustand store to manage both products and cart.
-
Display products with an βAdd to Cartβ button.
-
Add a
<Header />that shows total cart items dynamically. -
Use shared global state (
cart) with Zustand to sync across components.
π Step-by-Step Extension
π§ 1. Update db.json
Extend your db.json:
π§ 2. Update Zustand Store: store/shopStore.js
π§± 3. Create components/Header.js
π 4. Create components/ProductList.js
π 5. Update pages/index.js
β Now Your App Supports
-
Viewing products
-
Adding to cart
-
Shared
cartstate -
Real-time cart count in header
-
All logic managed by Zustand
