createStore
Create reactive stores with automatic TypeScript inference and built-in Immer support
API Reference
Syntax
const store = createStore(initialState);
Parameters
initialState
: Object defining your store's initial state and actions
Returns
A reactive store instance with:
value
: Current state (read/write access with dependency tracking)setState(updater, options?)
: Update state with Immer supportsubscribe(options)
: Fine-grained subscription control
Examples
Simple State Management
import { createStore, useStoreValue } from "zenbox";
// Types are automatically inferred
const counterStore = createStore({ count: 0 });
// Direct access state value via `value` property
console.log("Current count:", counterStore.value.count);
function Counter() {
// Reactive subscription for components
const { count } = useStoreValue(counterStore);
const increment = () => {
counterStore.setState((state) => {
state.count++; // Immer-powered mutations
});
};
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>+</button>
</div>
);
}
Store with Actions
Co-locate state and behavior for clean, maintainable code:
const userStore = createStore({
// State
name: "Del Wang",
blog: "https://del.wang",
followers: 0,
// Actions
follow() {
userStore.setState((state) => {
state.followers++;
});
},
async fetchProfile() {
const response = await fetch("https://api.github.com/users/del-wang");
const data = await response.json();
userStore.setState(data);
},
});
// Clean API calls
userStore.value.follow();
await userStore.value.fetchProfile();
Update Patterns
1. Immer-style Updates (Recommended)
Perfect for complex mutations and nested state:
store.setState((state) => {
state.count += 1;
state.user.profile.name = "Updated";
state.todos.push({ id: Date.now(), text: "New todo", completed: false });
});
2. Partial Object Updates
Merge new properties with existing state:
const store = createStore({
name: "John",
age: 25,
email: "john@example.com",
avatar: "avatar.jpg",
});
// Shallow merge with existing state
store.setState({ name: "Jane", age: 30 });
// Result: { name: "Jane", age: 30, email: "john@example.com", avatar: "avatar.jpg" }
// Explicit overwrites (including null/undefined)
store.setState({ avatar: null });
// Result: { name: "Jane", age: 30, email: "john@example.com", avatar: null }
// Unknown properties are ignored
store.setState({ unknownField: "ignored" }); // No effect
// Result: { name: "Jane", age: 30, email: "john@example.com", avatar: null }
3. Direct Assignment
For complete state replacement or when you prefer explicit assignment:
// Complete replacement
store.value = { count: 1, name: "Jane" };
// Partial updates (same as setState)
store.value = { count: 5 }; // Merges: { count: 5, name: "Jane" }
4. Silent Updates
Update state without triggering subscriptions / re-renders:
// Background cache updates
store.setState({ cache: freshData }, { silent: true });
Best Practices
✅ Co-locate state and actions
const store = createStore({
count: 0,
increment() {
/* action logic */
},
});
✅ Use Immer-style for complex updates
store.setState((state) => {
state.nested.property = newValue;
});
✅ Handle async operations properly
async fetchData() {
store.setState(state => { state.loading = true; });
try {
// API call
} finally {
store.setState(state => { state.loading = false; });
}
}
✅ Use silent updates for background data
store.setState({ cache: data }, { silent: true });
Related APIs
createProvider
- Component-scoped storesuseStoreValue
- Subscribe to store changesuseComputed
- Derived reactive valuesuseWatch
- Side effects on store changes