A powerful, lightweight, and fully standalone state management library for React with time-travel debugging, computed values, and zero dependencies
npm install zust"todos.0.done")
example/ folder to see all features in action. To run it:
bash
cd example
npm install # or bun install
npm run dev # or bun dev
`
Then open http://localhost:3000 to see the interactive demo.
Table of Contents
- Installation
- Quick Start
- Core Concepts
- Basic Usage
- Array Paths
- Async Operations
- Advanced Features
- Time-Travel Debugging
- Computed Values
- Path-Based Subscriptions
- Batched Updates
- Persistence
- API Reference
- Migration Guide
- License
- Contributors
Installation
`bash
npm install zust
`
or
`bash
bun install zust
`
Quick Start
`typescript
import { createStore } from 'zust';
// Define your initial state
const initialState = {
user: { name: 'John', age: 30 },
todos: [
{ id: 1, text: 'Learn Zust', done: false },
{ id: 2, text: 'Build app', done: false }
],
settings: { theme: 'light' as 'light' | 'dark' },
};
// Create the store
const { useSelectors, setDeep, getState } = createStore(initialState);
function App() {
// Select multiple values efficiently
const { name, theme } = useSelectors('user.name', 'settings.theme');
// Update nested state with ease
const updateName = () => setDeep('user.name', 'Jane');
const toggleTheme = () => setDeep('settings.theme',
prev => prev === 'light' ? 'dark' : 'light'
);
return (
User: {name}
Theme: {theme}
);
}
`
Core Concepts
$3
Zust uses dot-notation paths to access and update deeply nested state:
`typescript
const { setDeep, getState } = createStore({
user: {
profile: {
name: 'John',
email: 'john@example.com'
},
preferences: {
notifications: true
}
}
});
// Update nested values
setDeep('user.profile.name', 'Jane');
setDeep('user.preferences.notifications', false);
// Use functional updates
setDeep('user.profile.name', prevName => prevName.toUpperCase());
// Access current state
const currentState = getState();
console.log(currentState.user.profile.name); // 'JANE'
`
$3
Zust has native support for array indices in paths:
`typescript
const { setDeep } = createStore({
todos: [
{ id: 1, text: 'Task 1', done: false },
{ id: 2, text: 'Task 2', done: false }
]
});
// Update array items using index notation
setDeep('todos.0.done', true);
setDeep('todos.1.text', 'Updated Task 2');
// Works with nested arrays
setDeep('matrix.0.1.value', 42);
`
$3
Dispatch async actions with first-class async/await support:
`typescript
const store = createStore({
data: null,
loading: false,
error: null
});
const state = store.getState();
// Dispatch async actions
await state.dispatch(async (state, setDeep) => {
setDeep('loading', true);
try {
const response = await fetch('/api/data');
const data = await response.json();
setDeep('data', data);
} catch (error) {
setDeep('error', error.message);
} finally {
setDeep('loading', false);
}
});
`
Advanced Features
$3
Enable undo/redo functionality with built-in history management:
`typescript
const { getState, history } = createStore(
{ counter: 0 },
{
history: {
enabled: true,
maxSize: 50, // Maximum history entries (default: 50)
debounceMs: 100 // Debounce captures (default: 100ms)
}
}
);
const state = getState();
// Make some changes
setDeep('counter', 1);
setDeep('counter', 2);
setDeep('counter', 3);
// Undo/redo
if (history?.canUndo()) {
history.undo(); // counter is now 2
}
if (history?.canRedo()) {
history.redo(); // counter is back to 3
}
// Jump to specific state
history?.jump(-2); // Go back 2 states
// Clear history
history?.clear();
`
$3
Define cached computed properties that automatically recompute when dependencies change:
`typescript
const { getState } = createStore(
{
firstName: 'John',
lastName: 'Doe',
items: [{ price: 10 }, { price: 20 }]
},
{
computedValues: {
// Simple computed value
fullName: (state) => ${state.firstName} ${state.lastName},
// Computed value with explicit dependencies
total: {
compute: (state) => state.items.reduce((sum, item) => sum + item.price, 0),
deps: ['items'], // Only recompute when items change
cache: true // Cache the result (default: true)
}
}
}
);
const state = getState();
console.log(state.fullName); // 'John Doe'
console.log(state.total); // 30
// Computed values update automatically
setDeep('firstName', 'Jane');
console.log(getState().fullName); // 'Jane Doe'
`
$3
Subscribe to changes on specific paths for optimal performance:
`typescript
const { subscribePath } = createStore({
user: { name: 'John', age: 30 },
settings: { theme: 'light' }
});
// Subscribe to specific path
const unsubscribe = subscribePath('user.name', (newValue, oldValue, fullState) => {
console.log(Name changed from ${oldValue} to ${newValue});
});
setDeep('user.name', 'Jane'); // Triggers callback
setDeep('user.age', 31); // Does NOT trigger callback
// Unsubscribe when done
unsubscribe();
`
$3
Batch multiple updates to minimize re-renders:
`typescript
import { batch } from 'zust';
const { setDeep, subscribe } = createStore({ a: 0, b: 0, c: 0 });
let renderCount = 0;
subscribe(() => renderCount++);
// Without batching: 3 renders
setDeep('a', 1);
setDeep('b', 2);
setDeep('c', 3);
// With batching: 1 render
batch(() => {
setDeep('a', 1);
setDeep('b', 2);
setDeep('c', 3);
});
`
Persistence
Persist state to localStorage or sessionStorage:
`typescript
import { createStore, createPersistConfig } from 'zust';
const { useSelectors, setDeep } = createStore(
{
user: { name: 'John', age: 30 },
settings: { theme: 'light', language: 'en' }
},
{
persist: createPersistConfig('user', 'settings.theme'),
prefix: 'myapp' // localStorage key prefix
}
);
// Persist entire store
const store2 = createStore(initialState, { persist: true });
`
API Reference
$3
Creates a Zust store with the provided initial state and options.
Parameters:
- initialState: T - The initial state object (must be non-null object)
- options?: StoreOptions - Configuration options
Returns: StoreCreationResult containing:
- useStore() - React hook that returns the enhanced store
- useSelectors(...paths) - Hook to select multiple state values
- getState() - Get current state with methods (dispatch, setDeep, etc.)
- setState(partial, replace?) - Set state (shallow or deep merge)
- setDeep(path, value) - Update nested state by path
- subscribe(listener) - Subscribe to all state changes
- subscribePath(path, callback) - Subscribe to specific path changes
- destroy() - Cleanup and destroy the store
- history? - History API (if history is enabled)
$3
- persist?: boolean | PersistConfig - Enable state persistence
- prefix?: string - Prefix for localStorage keys
- logging?: boolean - Enable console logging
- middleware?: Middleware - Array of middleware functions
- computedValues?: ComputedValues - Computed properties definition
- plugins?: Plugin - Store plugins
- history?: HistoryConfig - Time-travel debugging configuration
$3
- enabled: boolean - Enable history tracking
- maxSize?: number - Maximum history entries (default: 50)
- debounceMs?: number - Debounce delay for captures (default: 100ms)
$3
Batch multiple state updates into a single notification.
$3
The store returned by getState() or useStore() includes:
- setDeep(path, action) - Update state by path
- dispatch(asyncAction) - Execute async actions
- subscribe(listener) - Subscribe to state changes
- subscribePath(path, callback) - Subscribe to path changes
- deleteDeep(path) - Delete property by path
- hasPath(path) - Check if path exists
- history? - Undo/redo API (if enabled)
Migration Guide
$3
Version 1.0 is a complete rewrite with breaking changes:
What's New:
- ✅ Fully standalone (no Zustand dependency)
- ✅ Array path support
- ✅ Time-travel debugging
- ✅ Async dispatch
- ✅ Computed values with caching
- ✅ Path-based subscriptions
- ✅ Better TypeScript types
Breaking Changes:
- Removed Zustand middleware compatibility
- getState() now returns enhanced store (not raw state)
- Internal engine completely rewritten
Migration Steps:
1. Update imports (API is mostly backward compatible)
2. Remove any Zustand-specific middleware
3. Update TypeScript types if using advanced features
4. Test your application thoroughly
Tests
Zust includes comprehensive tests:
- All 69 tests passing ✅
- Integration tests with React
- Unit tests for all features
- Security tests (prototype pollution protection)
- Edge case handling
Run tests:
`bash
npm test
``
Kamyar Taher 🚇 ⚠️ 💻 |