@chris39704/ezstate
v1.0.6
Published
Tiny TS state manager with actions, views, patches & snapshots
Downloads
20
Maintainers
Readme
EZState
Tiny. Typed. Time-travelling. Your minimalist MST-style state manager, zero dependencies, lightning fast.
🚀 Installation
bash npm install ezstate
or
yarn add ezstate
✨ Key Features
- Actions & computed views via createModel
- Proxy-tracked getters: just write a view function, EZState auto-tracks dependencies
- Snapshots & patches: time-travel, undo/redo, or hydrate SSR state
- Zero runtime deps: ~50 lines of code
- Universal support: ESM, CJS, Node SSR, browser & Next.js
- Full TypeScript support: infer your state, actions, and views
🔧 Quickstart
ts import { createModel } from 'ezstate'
- Define a model
const counter = createModel({ initialState: { a: 1, b: 2 }, actions: { incA: (s, by = 1) => ({ a: s.a + by }), incB: (s, by = 1) => ({ b: s.b + by }), }, views: { sum: s => s.a + s.b, isEvenA: s => s.a % 2 === 0, } })
// 2) Use it anywhere
console.log(counter.sum) // 3 counter.incA(5) console.log(counter.sum) // 8
// 3) Snapshots & patches
const snapshot = counter.getSnapshot() counter.incB(2) console.log(counter.getPatches()) // [ { op: 'replace', path: '/b', value: 4 } ]
// undo last change
counter.applyPatch([{ op: 'replace', path: '/b', value: snapshot.b }]) console.log(counter.getState()) // { a: 6, b: 2 }
🔄 React / Next.js Integration
ts // use in React components
import { useSyncExternalStore } from 'react' import { counter } from './models/counter'
function useCounter() { return useSyncExternalStore( counter.subscribe, () => counter.getState() ) }
// Hydration in pages/_app.js
import { useEffect } from 'react' import { counter } from '@/models/counter'
let hydrated = false export default function MyApp({ Component, pageProps }) { useEffect(() => { if (!hydrated && pageProps.initialState) { // hydrate by replacing state counter.setState(pageProps.initialState) hydrated = true } }, [pageProps.initialState])
return <Component {...pageProps} /> }
🛠️ API Reference
createModel(def)
- initialState: S — your starting state object
- actions: { [K: string]: (state: S, ...args) => Partial } — update functions
- views?: { [K: string]: (state: S) => any } — computed values
Returns a model with:
- getState(): S — read current state
- subscribe(fn: (s: S) => void): () => void — listen for changes
- (...args): void — invoke your actions
- : any — read computed views as getters
- getSnapshot(): S — deep clone state
- onPatch(fn: (ops: JsonPatch) => void): () => void — listen to patch events
- getPatches(): JsonPatch — get full patch history
- applyPatch(ops: JsonPatch): void — apply JSON-Patch ops
❤️ Why EZState?
- Bundle-friendly: tiny footprint
- Predictable: explicit, immutable-style state updates
- Performant: views recalc only on real changes
- Extensible: hook into patches for undo/redo, logging, persistence
Happy state managing! 🎉
