A React state monitor for Zustand stores with automatic registration and debugging capabilities.
🚀 Live Demo
Try out the interactive demo to see Zustand State Monitor in action!
- 🔍 Monitor Zustand State Changes: Track all state changes with detailed history
- 🔄 Automatic Registration: Auto-register Zustand stores with minimal setup
- 🎣 React Hooks: Easy-to-use hooks for React integration
- 📊 Dev Tools: Built-in visual debugging interface
- 🎯 Selective Monitoring: Monitor specific stores or filter by criteria
- 📝 Type Safe: Full TypeScript support
- 🚀 Zero Dependencies: No additional dependencies beyond React and Zustand
npm install zustand-state-monitor
# or
yarn add zustand-state-monitorimport { create } from 'zustand';
import { useStateMonitor, registerExistingStore } from 'zustand-state-monitor';
// Create your Zustand store
const useCountStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
}));
// Register the store for monitoring
registerExistingStore('countStore', useCountStore);
function App() {
const monitor = useStateMonitor();
// Your component logic here
return <div>Your App</div>;
}import { create } from 'zustand';
import { withAutoRegister } from 'zustand-state-monitor';
// Automatically register store on creation
const useCountStore = withAutoRegister('countStore', () =>
create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}))
);import { useStateHistory, useStateListener } from 'zustand-state-monitor';
function DebugComponent() {
// Get state change history
const { history, clearHistory } = useStateHistory('countStore');
// Listen to state changes
useStateListener((change) => {
console.log('State changed:', change);
}, ['countStore']);
return (
<div>
<h3>State History ({history.length})</h3>
<button onClick={clearHistory}>Clear History</button>
{/* Render history */}
</div>
);
}import { StateMonitorDevTools } from 'zustand-state-monitor';
function App() {
return (
<div>
{/* Your app content */}
<StateMonitorDevTools />
</div>
);
}The main monitoring class that tracks state changes.
import { StateMonitor } from 'zustand-state-monitor';
const monitor = new StateMonitor({
enabled: true,
maxHistorySize: 100,
debugMode: false,
logChanges: true
});Get access to the global state monitor instance.
const monitor = useStateMonitor();Get state change history for a specific store or all stores.
const { history, clearHistory } = useStateHistory('myStore');Listen to state changes across stores.
useStateListener((change) => {
console.log('State changed:', change);
}, ['store1', 'store2']);Manage store registration with automatic cleanup.
const { isRegistered, register, unregister } = useStoreRegistration(
'myStore',
myStore,
true // auto register
);Register an existing Zustand store.
registerExistingStore('myStore', myZustandStore);Wrap store creation with automatic registration.
const useMyStore = withAutoRegister('myStore', () =>
create((set) => ({ /* store config */ }))
);Set up global auto-registration for all Zustand stores (experimental).
setupGlobalAutoRegister({
debugMode: true,
exclude: ['temporaryStore']
});interface StateMonitorConfig {
enabled?: boolean; // Enable/disable monitoring
maxHistorySize?: number; // Maximum history entries per store
debugMode?: boolean; // Enable debug logging
autoRegister?: boolean; // Auto-register new stores
logChanges?: boolean; // Log changes to console
filters?: {
storeNames?: string[]; // Only monitor specific stores
excludeKeys?: string[]; // Exclude specific state keys
};
}interface StateChange<T = any> {
storeName: string; // Name of the store
oldState: T; // Previous state
newState: T; // New state
timestamp: number; // Change timestamp
diff?: Partial<T>; // State differences
}import React from 'react';
import { create } from 'zustand';
import {
registerExistingStore,
useStateMonitor,
useStateHistory,
StateMonitorDevTools
} from 'zustand-state-monitor';
// Create stores
const useCountStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
}));
const useUserStore = create((set) => ({
user: null,
setUser: (user) => set({ user }),
logout: () => set({ user: null }),
}));
// Register stores
registerExistingStore('counter', useCountStore);
registerExistingStore('user', useUserStore);
function Counter() {
const { count, increment, decrement } = useCountStore();
const { history } = useStateHistory('counter');
return (
<div>
<h2>Counter: {count}</h2>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
<p>Changes: {history.length}</p>
</div>
);
}
function App() {
const monitor = useStateMonitor();
return (
<div>
<h1>State Monitor Demo</h1>
<Counter />
<div>
<h3>Monitor Status</h3>
<p>Registered stores: {monitor.getRegisteredStores().join(', ')}</p>
<p>Total changes: {monitor.getHistory().length}</p>
</div>
{/* Dev Tools */}
<StateMonitorDevTools />
</div>
);
}
export default App;- Register stores early: Register your stores as early as possible in your app lifecycle
- Use meaningful names: Give your stores descriptive names for easier debugging
- Limit history size: Set appropriate
maxHistorySizeto prevent memory issues - Development only: Consider disabling in production or use feature flags
- Cleanup: The hooks handle cleanup automatically, but manual registration should be paired with unregistration
Full TypeScript support with proper type inference:
interface CountState {
count: number;
increment: () => void;
}
const useCountStore = create<CountState>((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}));
// Type-safe registration
registerExistingStore<CountState>('counter', useCountStore);
// Type-safe history
const { history } = useStateHistory<CountState>('counter');MIT