A tiny React state manager with explicit actions, readonly state, and selective subscriptions.
English · 简体中文
tinystore is a React-first store for teams that want a tiny API surface and a
hard mutation boundary:
Statedefines dataActionis the only place allowed to update state- components subscribe with
useStore(selector)
If you want a wide ecosystem or multiple store paradigms, use Zustand,
Jotai, or Nano Stores. If you want one small, explicit model, tinystore
is the point.
- Class-based
State + Actionmodel - Readonly state snapshots outside actions
useStore(selector)for selective subscriptionssubscribe(...)for non-React integrations- Async actions and function-style updates
pnpm add @cceevv/tinystore
# or
yarn add @cceevv/tinystore
# or
npm i @cceevv/tinystoreimport tinyStore, { Getter, Setter } from "@cceevv/tinystore";
class CounterState {
count = 0;
label = "hello";
}
class CounterAction {
constructor(
private get: Getter<CounterState>,
private set: Setter<CounterState>,
) {}
inc() {
this.set((prev) => ({ count: prev.count + 1 }));
}
setLabel(label: string) {
this.set({ label });
}
}
export const counterStore = tinyStore(CounterState, CounterAction);import { counterStore } from "./store";
export function Counter() {
const count = counterStore.useStore((state) => state.count);
const label = counterStore.useStore((state) => state.label);
const { inc, setLabel } = counterStore.actions();
return (
<>
<p>{label}</p>
<p>{count}</p>
<button onClick={inc}>inc</button>
<button onClick={() => setLabel("updated")}>set label</button>
</>
);
}StateClass should be a simple class with public fields.
class ProfileState {
nickname = "";
age = 0;
}ActionClass receives get and set in its constructor. State updates should
go through set.
class ProfileAction {
constructor(
private get: Getter<ProfileState>,
private set: Setter<ProfileState>,
) {}
birthday() {
this.set((prev) => ({ age: prev.age + 1 }));
}
}Use selectors by default. useStore() without arguments subscribes to the whole
state snapshot.
const age = profileStore.useStore((state) => state.age);
const profile = profileStore.useStore();Creates a store and returns:
useStoregetStoresubscribeactions
Returns the full readonly state snapshot.
const state = store.useStore();Returns the selected value and only rerenders when the selected result changes.
const count = store.useStore((state) => state.count);
const listLength = store.useStore((state) => state.list.length, Object.is);Returns the current readonly state snapshot. It can be used outside React components.
const snapshot = store.getStore();Subscribes to the full state. The listener receives (next, prev).
const unsubscribe = store.subscribe((next, prev) => {
console.log(next, prev);
});Subscribes to a selected value outside React.
const unsubscribe = store.subscribe(
(state) => state.count,
(next, prev) => {
console.log(next, prev);
},
);Returns the readonly action map.
const { inc } = store.actions();- State snapshots are readonly outside actions.
- Nested objects should be replaced immutably when updated.
tinystoreis React-first, not a cross-framework store core.tinystoreis not a deep reactive proxy system.
| Library | Primary model | Mutation boundary | Selective subscriptions | Cross-framework |
|---|---|---|---|---|
| tinystore | State + Action classes |
Explicit actions | Yes | No |
| Zustand | Function store | Convention-based | Yes | No |
| Jotai | Atoms | Atom writes | Yes | No |
| Nano Stores | Atoms/stores | Store APIs | Yes | Yes |
useStore(true)was removed.getStore(true)was removed.useStore(selector)is now the recommended subscription API.- State snapshots are always readonly.
To keep state structure and mutation entry points explicit and easy to scan in code review.
Because tinystore treats explicit actions as a core design constraint, not an
optional pattern.
The public API is React-first. It also exposes getStore() and subscribe()
for non-React integrations.
tinystore trades ecosystem breadth for a narrower model with harder mutation
boundaries.
MIT License (c) cceevv