@volt-package/store
v0.1.0
Published
A state management library with automatic dependency tracking, structured architecture, and first-class async support
Maintainers
Readme
@volt-package/store
A modern state management library that goes beyond Zustand with automatic dependency tracking, structured architecture, and first-class async support.
Why Volt Store?
Volt Store addresses the key pain points of existing state management solutions:
1. No More Selector Hell (Automatic Dependency Tracking)
The Problem with Zustand:
// You must manually write selectors to avoid unnecessary re-renders
const bears = useStore(state => state.bears); // Forget this? Performance issues!Volt Store Solution:
// Just use the state - automatic tracking handles the rest!
const { state } = useStore(store);
console.log(state.bears); // Automatically subscribes to only `bears` changesUses Proxy-based automatic dependency tracking (like Vue 3 Reactivity, Signals, or Legend-State).
2. Enforced Structure (Modular Architecture)
The Problem with Zustand:
// State and actions mixed together - becomes spaghetti code in large apps
create((set) => ({
count: 0,
increment: () => set(state => ({ count: state.count + 1 })),
user: { name: 'Alice' },
updateUser: (name) => set({ user: { name } })
}))Volt Store Solution:
// Clear separation of concerns
createStore({
state: {
count: 0,
user: { name: 'Alice' }
},
actions: (state) => ({
increment() {
state.count++; // Direct, intuitive mutation
},
updateUser(name: string) {
state.user.name = name;
}
})
})3. First-Class Async Support
The Problem with Zustand:
// You manually track loading states
const useStore = create((set) => ({
data: null,
isLoading: false,
error: null,
fetchData: async () => {
set({ isLoading: true });
try {
const data = await api.get();
set({ data, isLoading: false });
} catch (error) {
set({ error, isLoading: false });
}
}
}))Volt Store Solution:
// Async states are automatically managed!
const store = createStore({
state: { data: null },
actions: (state) => ({
async fetchData() {
const data = await api.get();
state.data = data;
}
})
});
// In React component:
const { asyncStates } = useStore(store);
console.log(asyncStates.fetchData.status); // 'idle' | 'pending' | 'fulfilled' | 'rejected'
console.log(asyncStates.fetchData.error); // Auto-populated on error4. Mutable Syntax, Immutable Results
The Problem with Zustand:
// Deep updates are painful without Immer middleware
set(state => ({
nested: {
...state.nested,
deep: {
...state.nested.deep,
value: newValue
}
}
}))Volt Store Solution:
// Write like it's mutable, but it's actually immutable under the hood!
actions: (state) => ({
updateDeep(value) {
state.nested.deep.value = value; // Just works! (Powered by Immer-like proxy)
}
})Installation
npm install @volt-package/store
# or
bun add @volt-package/storeQuick Start
Basic Store
import { createStore } from '@volt-package/store';
const counterStore = createStore({
state: {
count: 0
},
actions: (state) => ({
increment() {
state.count++;
},
decrement() {
state.count--;
},
add(value: number) {
state.count += value;
}
})
});
// Use outside React
counterStore.actions.increment();
console.log(counterStore.state.count); // 1React Integration
import { useStore } from '@volt-package/store/react';
function Counter() {
const { state, actions } = useStore(counterStore);
// Automatic dependency tracking - only re-renders when `count` changes!
return (
<div>
<p>Count: {state.count}</p>
<button onClick={actions.increment}>+</button>
<button onClick={actions.decrement}>-</button>
</div>
);
}Complex State Management
interface Todo {
id: number;
text: string;
done: boolean;
}
const todoStore = createStore({
state: {
todos: [] as Todo[],
filter: 'all' as 'all' | 'active' | 'completed'
},
actions: (state) => ({
addTodo(text: string) {
state.todos.push({
id: Date.now(),
text,
done: false
});
},
toggleTodo(id: number) {
const todo = state.todos.find(t => t.id === id);
if (todo) {
todo.done = !todo.done;
}
},
removeTodo(id: number) {
const index = state.todos.findIndex(t => t.id === id);
if (index !== -1) {
state.todos.splice(index, 1);
}
},
setFilter(filter: 'all' | 'active' | 'completed') {
state.filter = filter;
}
})
});Async Actions
const userStore = createStore({
state: {
user: null as User | null,
posts: [] as Post[]
},
actions: (state) => ({
async fetchUser(id: string) {
const user = await api.getUser(id);
state.user = user;
return user;
},
async fetchPosts() {
const posts = await api.getPosts();
state.posts = posts;
return posts;
}
})
});
// In React component:
function UserProfile() {
const { state, actions, asyncStates } = useStore(userStore);
useEffect(() => {
actions.fetchUser('123');
}, []);
if (asyncStates.fetchUser.status === 'pending') {
return <div>Loading...</div>;
}
if (asyncStates.fetchUser.status === 'rejected') {
return <div>Error: {asyncStates.fetchUser.error?.message}</div>;
}
return <div>Welcome, {state.user?.name}!</div>;
}API Reference
createStore(config)
Creates a new store instance.
Parameters:
config.state: Initial state objectconfig.actions: Function that receives state and returns action methodsconfig.name(optional): Store name for debugging
Returns: Store instance with:
state: Reactive state proxyactions: Bound action methodsasyncStates: Async operation statessubscribe(listener): Subscribe to changesgetSnapshot(): Get current state snapshotdestroy(): Cleanup store
React Hooks
useStore(store)
Main hook for using store in React components with automatic dependency tracking.
const { state, actions, asyncStates } = useStore(myStore);useActions(store)
Get only actions (no state subscription).
const actions = useActions(myStore);useAsync(store, actionName)
Subscribe to specific async action state.
const asyncState = useAsync(myStore, 'fetchData');
console.log(asyncState.status, asyncState.error);useStoreSelector(store, selector, equalityFn?)
Advanced hook with manual selector (for edge cases).
const count = useStoreSelector(myStore, state => state.count);Advanced Features
Subscriptions
const unsubscribe = store.subscribe((state, prevState, changedKeys) => {
console.log('Changed keys:', changedKeys);
console.log('New state:', state);
});
// Later...
unsubscribe();Direct State Access (Non-Reactive)
// Get snapshot without triggering dependency tracking
const snapshot = store.getSnapshot();TypeScript Support
Volt Store is written in TypeScript and provides full type safety:
interface AppState {
count: number;
user: User | null;
}
const store = createStore({
state: {
count: 0,
user: null
} as AppState,
actions: (state) => ({
increment() {
state.count++; // ✓ Type-safe
},
setUser(user: User) {
state.user = user; // ✓ Type-safe
// state.user = 123; // ✗ Type error!
}
})
});Performance
Volt Store uses:
- Proxy-based tracking for zero-overhead dependency detection
- Structural sharing for efficient memory usage
- Batched updates to minimize re-renders
- Automatic memoization of unchanged nested objects
Comparison
| Feature | Zustand | Volt Store | |---------|---------|------------| | Bundle Size | ~1KB | ~3KB | | Auto Tracking | ✗ (manual selectors) | ✓ (Proxy-based) | | State/Action Separation | ✗ | ✓ | | Async State Management | Manual | Automatic | | Mutable Syntax | With middleware | Built-in | | TypeScript | ✓ | ✓ | | DevTools | ✓ | Planned |
License
MIT
Contributing
Contributions are welcome! Please check out our GitHub repository.
Credits
Inspired by:
- Zustand - Simple API design
- Vue 3 Reactivity - Automatic dependency tracking
- Signals - Fine-grained reactivity
- Immer - Mutable syntax for immutable updates
- Pinia - Structured store architecture
