Skip to main content

Zustand Adapter

Track Zustand store updates in React Dev Debugger for better debugging experience.

Installation

npm install react-dev-debugger zustand

Basic Setup

store.ts
import create from 'zustand';
import { createZustandAdapter } from 'react-dev-debugger/adapters/zustand';

interface BearStore {
bears: number;
increase: () => void;
decrease: () => void;
}

export const useStore = create<BearStore>()((set) => ({
bears: 0,
increase: () => set((state) => ({ bears: state.bears + 1 })),
decrease: () => set((state) => ({ bears: state.bears - 1 })),
}));

// Connect debugger
createZustandAdapter(useStore, {
name: 'Bear Store',
});

Live Playground

Zustand Store

Counter
Count: 0
User State
No user set
Store Setup
import create from 'zustand';
import { createZustandAdapter } from 
  'react-dev-debugger/adapters/zustand';

const useStore = create((set) => ({
  count: 0,
  increment: () => set((state) => ({ 
    count: state.count + 1 
  })),
}));

createZustandAdapter(useStore, {
  name: 'My Store',
});

State Updates

No updates yet. Interact with the store above.

Zustand Features:

  • Track store updates
  • Monitor state changes
  • View action history
  • Simple integration

Usage in Components

BearCounter.tsx
import { useStore } from './store';

export function BearCounter() {
const bears = useStore((state) => state.bears);
const increase = useStore((state) => state.increase);
const decrease = useStore((state) => state.decrease);

return (
<div>
<h1>{bears} bears around here...</h1>
<button onClick={increase}>Add Bear</button>
<button onClick={decrease}>Remove Bear</button>
</div>
);
}

Multiple Stores

stores.ts
import create from 'zustand';
import { createZustandAdapter } from 'react-dev-debugger/adapters/zustand';

// User store
export const useUserStore = create((set) => ({
user: null,
login: (user) => set({ user }),
logout: () => set({ user: null }),
}));

createZustandAdapter(useUserStore, { name: 'User Store' });

// Cart store
export const useCartStore = create((set) => ({
items: [],
addItem: (item) => set((state) => ({
items: [...state.items, item],
})),
removeItem: (id) => set((state) => ({
items: state.items.filter(item => item.id !== id),
})),
}));

createZustandAdapter(useCartStore, { name: 'Cart Store' });

With Middleware

Zustand adapter works with all Zustand middleware:

import create from 'zustand';
import { persist } from 'zustand/middleware';
import { createZustandAdapter } from 'react-dev-debugger/adapters/zustand';

interface AppStore {
theme: 'light' | 'dark';
setTheme: (theme: 'light' | 'dark') => void;
}

export const useAppStore = create<AppStore>()(
persist(
(set) => ({
theme: 'light',
setTheme: (theme) => set({ theme }),
}),
{
name: 'app-storage',
}
)
);

createZustandAdapter(useAppStore, {
name: 'App Store (Persisted)',
});

Complete Example: Todo App

todoStore.ts
import create from 'zustand';
import { createZustandAdapter } from 'react-dev-debugger/adapters/zustand';

interface Todo {
id: number;
text: string;
completed: boolean;
}

interface TodoStore {
todos: Todo[];
filter: 'all' | 'active' | 'completed';
addTodo: (text: string) => void;
toggleTodo: (id: number) => void;
deleteTodo: (id: number) => void;
setFilter: (filter: TodoStore['filter']) => void;
clearCompleted: () => void;
}

export const useTodoStore = create<TodoStore>()((set) => ({
todos: [],
filter: 'all',

addTodo: (text) =>
set((state) => ({
todos: [
...state.todos,
{ id: Date.now(), text, completed: false },
],
})),

toggleTodo: (id) =>
set((state) => ({
todos: state.todos.map((todo) =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
),
})),

deleteTodo: (id) =>
set((state) => ({
todos: state.todos.filter((todo) => todo.id !== id),
})),

setFilter: (filter) => set({ filter }),

clearCompleted: () =>
set((state) => ({
todos: state.todos.filter((todo) => !todo.completed),
})),
}));

createZustandAdapter(useTodoStore, {
name: 'Todo Store',
maxSnapshots: 50,
});
TodoApp.tsx
import { useState } from 'react';
import { useTodoStore } from './todoStore';

export function TodoApp() {
const [input, setInput] = useState('');
const { todos, filter, addTodo, toggleTodo, deleteTodo, setFilter, clearCompleted } =
useTodoStore();

const filteredTodos = todos.filter((todo) => {
if (filter === 'active') return !todo.completed;
if (filter === 'completed') return todo.completed;
return true;
});

const handleAdd = () => {
if (input.trim()) {
addTodo(input);
setInput('');
}
};

return (
<div>
<h1>Zustand Todo List</h1>

<div>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && handleAdd()}
placeholder="What needs to be done?"
/>
<button onClick={handleAdd}>Add</button>
</div>

<div>
<button onClick={() => setFilter('all')}>All</button>
<button onClick={() => setFilter('active')}>Active</button>
<button onClick={() => setFilter('completed')}>Completed</button>
</div>

<ul>
{filteredTodos.map((todo) => (
<li key={todo.id}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleTodo(todo.id)}
/>
<span style={{
textDecoration: todo.completed ? 'line-through' : 'none'
}}>
{todo.text}
</span>
<button onClick={() => deleteTodo(todo.id)}>Delete</button>
</li>
))}
</ul>

{todos.some((t) => t.completed) && (
<button onClick={clearCompleted}>Clear Completed</button>
)}
</div>
);
}

Configuration Options

interface ZustandAdapterOptions {
// Display name in debugger
name?: string;

// Maximum snapshots to keep
maxSnapshots?: number;

// Transform state before displaying
stateTransform?: (state: any) => any;

// Track only specific state slices
trackSlices?: string[];
}

Debugging Tips

Selective State Tracking

Track only parts of your state:

createZustandAdapter(useStore, {
name: 'App Store',
trackSlices: ['user', 'cart'], // Only track these slices
});

State Transformation

Hide sensitive or verbose data:

createZustandAdapter(useStore, {
name: 'User Store',
stateTransform: (state) => ({
user: {
id: state.user?.id,
name: state.user?.name,
// Hide sensitive fields
token: '[REDACTED]',
},
}),
});

Next Steps