Basic Store
A lightweight state management solution with batched updates, computed properties, path-based subscriptions, and a composable middleware system.
Basic Usage
Counter Store
A simple counter using create() and createHook() for React integration
import { create, createHook } from 'fluxo-ui/store';
interface CounterState {
count: number;
}
const counterStore = create<CounterState>(() => ({ count: 0 }));
const useCounter = createHook(counterStore);
function Counter() {
const { count } = useCounter();
return (
<div>
<span>Count: {count}</span>
<Button label="Increment"
onClick={() => counterStore.setState(s => ({ count: s.count + 1 }))} />
<Button label="Decrement"
onClick={() => counterStore.setState(s => ({ count: s.count - 1 }))} />
<Button label="Reset" variant="secondary"
onClick={() => counterStore.reset()} />
</div>
);
}Computed Properties
Synchronous Computed Properties
Derived state that updates automatically when dependencies change
import { create, createHook } from 'fluxo-ui/store';
const userStore = create<UserState>(() => ({
firstName: 'John',
lastName: 'Doe',
}));
userStore.compute(
'fullName',
(state) => `${state.firstName} ${state.lastName}`,
['firstName', 'lastName']
);
userStore.compute(
'initials',
(state) => `${state.firstName.charAt(0)}${state.lastName.charAt(0)}`.toUpperCase(),
['firstName', 'lastName']
);
const useUser = createHook(userStore);
function UserCard() {
const state = useUser();
const { firstName, lastName, fullName, initials } = state as any;
return (
<div>
<div className="avatar">{initials}</div>
<div>{fullName}</div>
<TextInput value={firstName}
onChange={(e) => userStore.setState({ firstName: e.value })} />
<TextInput value={lastName}
onChange={(e) => userStore.setState({ lastName: e.value })} />
</div>
);
}Async Computed Properties
Computed properties that resolve asynchronously with automatic loading state
import { create, createHook } from 'fluxo-ui/store';
const asyncStore = create<{ userId: number }>(() => ({ userId: 1 }));
asyncStore.compute(
'profile',
async (state) => {
const res = await fetch(`/api/users/${state.userId}`);
return res.json();
},
['userId']
);
const useStore = createHook(asyncStore);
function UserProfile() {
const state = useStore() as any;
// state.profile → the resolved value (undefined while loading)
// state.profileLoading → boolean (true while fetching)
if (state.profileLoading) return <p>Loading profile...</p>;
return <p>{state.profile}</p>;
}Batched Updates
Batched Updates
Multiple setState calls within one synchronous handler are batched into a single re-render
import { create, createHook } from 'fluxo-ui/store';
const store = create<{ count: number }>(() => ({ count: 0 }));
const useStore = createHook(store);
function BatchDemo() {
const renderCount = useRef(0);
renderCount.current++;
const { count } = useStore();
const handleBatchIncrement = () => {
// All three calls are batched into a single re-render
store.setState((s) => ({ count: s.count + 1 }));
store.setState((s) => ({ count: s.count + 1 }));
store.setState((s) => ({ count: s.count + 1 }));
};
return (
<div>
<p>Count: {count}</p>
<p>Render count: {renderCount.current}</p>
<Button label="+3 (Batched)" onClick={handleBatchIncrement} />
</div>
);
}Path Subscriptions
Path-Based Subscriptions
Each component subscribes to a specific slice of state using useProfile(selector, true). The second argument enables shallow comparison. Change user data — only UserCard re-renders. Change settings — only SettingsPanel re-renders. The Full State Watcher (no selector) re-renders on every change.
{
"user": {
"name": "Alice",
"email": "alice@example.com",
"role": "Admin"
},
"settings": {
"theme": "light",
"notifications": true,
"language": "English"
},
"metadata": {
"lastLogin": "4:25:09 PM",
"version": 1
}
}import { create, createHook } from 'fluxo-ui/store';
const profileStore = create<ProfileState>(() => ({
user: { name: 'Alice', email: 'alice@example.com', role: 'Admin' },
settings: { theme: 'light', notifications: true, language: 'English' },
metadata: { lastLogin: '...', version: 1 },
}));
const useProfile = createHook(profileStore);
// Hook with selector + shallow equality (true = shallow compare)
// Only re-renders when the user object reference changes
function UserCard() {
const user = useProfile((s) => s.user, true);
return <div>{user.name} ({user.role})</div>;
}
// Hook with selector — only re-renders when settings change
function SettingsPanel() {
const settings = useProfile((s) => s.settings, true);
return <div>{settings.theme} / {settings.language}</div>;
}
// Hook with no selector — re-renders on EVERY state change
function FullStateWatcher() {
const state = useProfile();
return <pre>{JSON.stringify(state, null, 2)}</pre>;
}
// External (non-React) path subscription
profileStore.on('change', 'user.name', (state) => {
console.log('Name changed:', state.user.name);
});
profileStore.on('change', 'settings', (state) => {
console.log('Settings changed:', state.settings);
});Multiple Stores
Multiple Independent Stores
Two separate stores (tasks + notifications) coexist in one component tree. Each store has its own hook — updating one never causes re-renders in components subscribed to the other. Watch the render counts to verify isolation.
import { create, createHook } from 'fluxo-ui/store';
// Store A — Task management
const taskStore = create<TaskState>(() => ({
tasks: [{ id: 1, title: 'Build components', done: false }],
nextId: 2,
filter: 'all',
}));
taskStore.compute('completedCount', (s) => s.tasks.filter(t => t.done).length, ['tasks']);
taskStore.compute('progress', (s) => {
const done = s.tasks.filter(t => t.done).length;
return s.tasks.length ? Math.round((done / s.tasks.length) * 100) : 0;
}, ['tasks']);
const useTaskStore = createHook(taskStore);
// Store B — Notification feed
const notificationStore = create<NotificationState>(() => ({
items: [],
nextId: 1,
muted: false,
}));
const useNotificationStore = createHook(notificationStore);
// Each store is fully independent — updating one never triggers
// re-renders in components that only subscribe to the other.
function TaskPanel() {
const { tasks, filter } = useTaskStore();
// Only re-renders when taskStore changes
}
function NotificationFeed() {
const { items, muted } = useNotificationStore();
// Only re-renders when notificationStore changes
}Import
import { create, createHook } from 'fluxo-ui/store';
import {
persistMiddleware,
undoRedoMiddleware,
validationMiddleware,
loggerMiddleware,
throttleMiddleware,
devToolsMiddleware,
} from 'fluxo-ui/store/middlewares';API Reference
create(initializer, middlewares?)(initializer: () => T, middlewares?: Middleware<T>[]) => Store<T>"-"Create a new store with initial state and optional middleware
create(initializer, middlewares?)(initializer: () => T, middlewares?: Middleware<T>[]) => Store<T>"-"Create a new store with initial state and optional middleware
store.getState()() => T"-"Get the current state snapshot (includes computed properties)
store.getState()() => T"-"Get the current state snapshot (includes computed properties)
store.setState(update)(partial: Partial<T>) => void"-"Merge partial state into current state (batched via microtask)
store.setState(update)(partial: Partial<T>) => void"-"Merge partial state into current state (batched via microtask)
store.setState(updater)(fn: (state: T) => Partial<T>) => void"-"Update state using an updater function for safe reads
store.setState(updater)(fn: (state: T) => Partial<T>) => void"-"Update state using an updater function for safe reads
store.on(event, listener)(event: 'init' | 'change', listener) => unsubscribe"-"Subscribe to all state changes or initialization
store.on(event, listener)(event: 'init' | 'change', listener) => unsubscribe"-"Subscribe to all state changes or initialization
store.on(event, path, listener)(event: 'change', path: string, listener) => unsubscribe"-"Subscribe to changes on a specific state path
store.on(event, path, listener)(event: 'change', path: string, listener) => unsubscribe"-"Subscribe to changes on a specific state path
store.reset()() => void"-"Reset state to the initial value from the initializer function
store.reset()() => void"-"Reset state to the initial value from the initializer function
store.compute(name, fn, deps)(name: string, fn: (state: T) => R, deps: string[]) => void"-"Register a computed property with dependency tracking
store.compute(name, fn, deps)(name: string, fn: (state: T) => R, deps: string[]) => void"-"Register a computed property with dependency tracking
createHook(store)(store: Store<T>) => useStore"-"Create a React hook bound to a store for reactive component integration
createHook(store)(store: Store<T>) => useStore"-"Create a React hook bound to a store for reactive component integration
useStore(selector?, equalityFn?)(selector?, equalityFn?) => T | R"-"React hook returned by createHook. Optional selector for derived slices
useStore(selector?, equalityFn?)(selector?, equalityFn?) => T | R"-"React hook returned by createHook. Optional selector for derived slices
Features
Microtask Batching
Multiple setState calls within one synchronous handler are batched into a single notification, minimizing re-renders
Computed Properties
Derive values from state with automatic dependency tracking and caching for optimal performance
Path-Based Subscriptions
Listen to specific state paths for fine-grained reactivity, avoiding unnecessary work when unrelated state changes
Middleware System
Extensible middleware for persistence, undo/redo, validation, throttling, logging, and devtools integration
Immutable Updates
State is cloned via structuredClone on every update, ensuring safe immutable operations without external libraries
Framework Agnostic Core
The core store is plain TypeScript with no React dependency. React integration is provided through createHook()