Zero-dependency reactive state management for any framework - featuring slices, computed values, effects, validation, and middleware support
npm install @oxog/state


Zero-dependency reactive state management for any framework.
- Zero Dependencies - No runtime dependencies, smaller bundle size
- Framework Agnostic - Works with React, Vue, Svelte, or vanilla JS
- TypeScript Native - Built with strict mode, full type inference
- Plugin System - Extend with persist, devtools, history, sync, logger, effects, validate
- Slices & Computed - Modular state organization with derived values
- Store Federation - Combine multiple stores for large applications
- Tiny Bundle - Less than 2KB gzipped for core
``bash`
npm install @oxog/state
`bash`
yarn add @oxog/state
`bash`
pnpm add @oxog/state
`typescript
import { createStore, useStore } from '@oxog/state';
// Create a store with state and actions
const store = createStore({
count: 0,
$increment: (state) => ({ count: state.count + 1 }),
$decrement: (state) => ({ count: state.count - 1 }),
});
// Use in React
function Counter() {
const count = useStore(store, (s) => s.count);
const increment = useStore(store, (s) => s.increment);
return ;
}
// Use in vanilla JS
store.subscribe((state) => console.log(state.count));
store.getState().increment();
`
#### createStore(initialState)
Creates a reactive store.
`typescript`
const store = createStore({
count: 0,
$increment: (state) => ({ count: state.count + 1 }),
});
#### batch(fn)
Batch multiple updates into a single notification.
`typescript
import { batch } from '@oxog/state';
batch(() => {
store.setState({ count: 1 });
store.setState({ name: 'John' });
}); // Single notification
`
| Method | Description |
|--------|-------------|
| getState() | Get current state |setState(partial)
| | Update state with partial object |merge(partial)
| | Deep merge state |reset()
| | Reset to initial state |subscribe(listener, selector?)
| | Subscribe to changes |use(plugin, options?)
| | Add plugin |destroy()
| | Cleanup store |
#### useStore(store, selector?, equalityFn?)
Subscribe to store state in React components.
`typescript
// Full state
const state = useStore(store);
// With selector
const count = useStore(store, (s) => s.count);
// With custom equality
const user = useStore(store, (s) => s.user, deepEqual);
`
#### useShallow(selector) v1.2
Wrap selectors returning objects to use shallow equality comparison.
`typescript
import { useStore, useShallow } from '@oxog/state';
// Without useShallow - re-renders on any state change
// const data = useStore(store, s => ({ a: s.a, b: s.b }));
// With useShallow - only re-renders when values change
const data = useStore(store, useShallow(s => ({ a: s.a, b: s.b })));
`
#### useStoreSelector(store, selectors) v1.2
Select multiple values with independent subscriptions.
`typescript
import { useStoreSelector } from '@oxog/state';
const { userCount, totalRevenue } = useStoreSelector(store, {
userCount: s => s.users.length,
totalRevenue: s => s.orders.reduce((sum, o) => sum + o.total, 0),
});
`
#### useStoreActions(store, ...actionNames) v1.2
Get multiple actions with stable references.
`typescript
import { useStoreActions } from '@oxog/state';
const { increment, decrement, reset } = useStoreActions(
store,
'increment', 'decrement', 'reset'
);
`
#### useSetState(store) v1.2
Get a stable setState function for partial updates.
`typescript
import { useSetState } from '@oxog/state';
function Form() {
const setState = useSetState(store);
return (
setState({ name: e.target.value })} />
);
}
`
#### useTransientSubscribe(store, selector, callback) v1.2
Subscribe to state changes without causing re-renders.
`typescript
import { useTransientSubscribe } from '@oxog/state';
function Analytics() {
useTransientSubscribe(
store,
s => s.pageViews,
(views) => analytics.track('page_view', { count: views })
);
return
####
useCreateStore(initialState)Create a store scoped to component lifecycle.
`typescript
function MyComponent() {
const store = useCreateStore({ count: 0 });
// Store is destroyed when component unmounts
}
`####
useAction(store, actionName)Get a stable action reference.
`typescript
const increment = useAction(store, 'increment');
`Patterns v1.2
$3
Organize state into modular, reusable slices.
`typescript
import { createStore, createSlice } from '@oxog/state';const userSlice = createSlice('user', {
name: '',
email: '',
$login: (state, name: string, email: string) => ({ name, email }),
$logout: () => ({ name: '', email: '' }),
});
const cartSlice = createSlice('cart', {
items: [],
$addItem: (state, item) => ({ items: [...state.items, item] }),
});
const store = createStore({
...userSlice,
...cartSlice,
});
// Access: store.getState().user.name
// Action: store.getState().user.login('John', 'john@example.com')
`$3
Derive values from state with automatic memoization.
`typescript
import { createStore, computed } from '@oxog/state';const store = createStore({
items: [{ price: 10, qty: 2 }, { price: 20, qty: 1 }],
totalItems: computed((state) =>
state.items.reduce((sum, i) => sum + i.qty, 0)
),
totalPrice: computed((state) =>
state.items.reduce((sum, i) => sum + i.price * i.qty, 0)
),
});
console.log(store.getState().totalItems); // 3
console.log(store.getState().totalPrice); // 40
`$3
Combine multiple stores into a unified interface.
`typescript
import { createStore, createFederation } from '@oxog/state';const userStore = createStore({ name: 'John' });
const cartStore = createStore({ items: [] });
const settingsStore = createStore({ theme: 'dark' });
const federation = createFederation({
user: userStore,
cart: cartStore,
settings: settingsStore,
});
// Access all stores
const state = federation.getState();
console.log(state.user.name, state.cart.items, state.settings.theme);
// Subscribe to specific store
federation.subscribeStore('user', (userState) => {
console.log('User changed:', userState);
});
`Plugins
$3
Persist state to localStorage with advanced options.
`typescript
import { persist } from '@oxog/state';const store = createStore({ count: 0, temp: '' })
.use(persist({
key: 'my-app',
storage: localStorage,
// v1.2.0 options
version: 1,
migrate: (state, version) => state,
partialize: (state) => ({ count: state.count }), // Only persist count
writeDebounce: 100,
onRehydrateStorage: (state) => console.log('Hydrated:', state),
}));
`$3
Connect to Redux DevTools Extension.
`typescript
import { devtools } from '@oxog/state';const store = createStore({ count: 0 })
.use(devtools({ name: 'My Store' }));
`$3
Add undo/redo functionality.
`typescript
import { history } from '@oxog/state';const store = createStore({ count: 0 })
.use(history({ limit: 50 }));
store.setState({ count: 1 });
store.setState({ count: 2 });
store.undo(); // { count: 1 }
store.redo(); // { count: 2 }
`$3
Synchronize state across browser tabs.
`typescript
import { sync } from '@oxog/state';const store = createStore({ count: 0 })
.use(sync({ channel: 'my-app' }));
`$3
Development logging with diff, timestamps, and filtering.
`typescript
import { logger } from '@oxog/state';const store = createStore({ count: 0 })
.use(logger({
level: 'debug',
collapsed: true,
diff: true,
timestamp: true,
filter: (action, state) => state.count > 0,
}));
`$3
Reactive side effects with debounce and cleanup.
`typescript
import { effects, createEffect } from '@oxog/state';const store = createStore({
searchTerm: '',
results: [],
})
.use(effects({
search: createEffect(
(state) => state.searchTerm,
async (term, { setState, signal }) => {
const res = await fetch(
/api/search?q=${term}, { signal });
const results = await res.json();
setState({ results });
},
{ debounce: 300 }
),
}));
`$3
Schema-agnostic validation with Zod, Yup, or custom validators.
`typescript
import { validate } from '@oxog/state';
import { z } from 'zod';const schema = z.object({
email: z.string().email(),
age: z.number().min(0).max(150),
});
const store = createStore({ email: '', age: 0 })
.use(validate({
schema,
timing: 'before',
rejectInvalid: true,
onError: (errors) => console.error(errors),
}));
`Async Actions
Handle async operations with async actions.
`typescript
const store = createStore({
data: null,
loading: false,
error: null,
$fetch: async (state, url: string) => {
store.setState({ loading: true, error: null });
try {
const res = await fetch(url);
const data = await res.json();
return { data, loading: false };
} catch (error) {
return { error, loading: false };
}
},
});await store.getState().fetch('/api/users');
`TypeScript
Full TypeScript support with type inference.
`typescript
import { createStore, createSlice, InferSliceState } from '@oxog/state';// Type inference from slices
const counterSlice = createSlice('counter', {
count: 0,
$increment: (state) => ({ count: state.count + 1 }),
});
type CounterState = InferSliceState;
// { counter: { count: number } }
// Explicit types
interface State {
count: number;
user: User | null;
}
const store = createStore({
count: 0,
user: null,
$increment: (s) => ({ count: s.count + 1 }),
$setUser: (s, user: User) => ({ user }),
});
`Testing v1.2
Testing utilities for stores.
`typescript
import { createTestStore, mockStore, getStoreSnapshot } from '@oxog/state/testing';// Isolated test store
const store = createTestStore({
count: 0,
$increment: (s) => ({ count: s.count + 1 }),
});
// Mock store with action tracking
const mock = mockStore({ count: 0, $increment: (s) => ({ count: s.count + 1 }) });
mock.getState().increment();
expect(mock.actionCalls.increment).toBe(1);
// Snapshot testing
const snapshot = getStoreSnapshot(store);
expect(snapshot).toMatchSnapshot();
``- Chrome 60+
- Firefox 55+
- Safari 12+
- Edge 79+
Full documentation available at state.oxog.dev
MIT