Skip to content

Stores

Create an Immer-backed store and mutate it through drafts.

@nice-code/state is a framework-agnostic, Immer-backed state container with fine-grained selector subscriptions, derived reactions, patch streams, and an optional React adapter.

Terminal window
bun add @nice-code/state immer
  • One immutable store, mutated with Immer — write plain mutations (s.count += 1), get structural sharing for free.
  • No-op updates are genuinely free — listeners fire only when the root reference actually changes.
  • Fine-grained selectors — components and side-effects re-run only when their watched slice changes structurally.
  • Tear-free React — built on useSyncExternalStore, no provider or context required.

Pass a value, or a factory function (re-used by SSR / reset).

import { Store } from "@nice-code/state";
interface ICounterState {
count: number;
step: number;
}
export const counterStore = new Store<ICounterState>({ count: 0, step: 1 });

Mutations run through Immer — mutate the draft freely; the store commits a new immutable state.

// Single update
counterStore.update((s) => {
s.count += s.step;
});
// Batched — applied in order, committed as one change
counterStore.update([
(s) => { s.count += 1; },
(s) => { s.count += 1; },
]);
// Replace the whole state
counterStore.replace({ count: 0, step: 1 });
// Replace by mapping from current state
counterStore.replaceFromCurrent((s) => ({ ...s, count: 0 }));

The second argument to an updater is a readonly snapshot of the pre-update state:

counterStore.update((draft, original) => {
draft.count = original.count * 2;
});
const { count } = counterStore.getRawState();

Reach for getRawState() only in plain logic. In components use useStoreState; for side-effects use watch.

useStoreState renders the store’s current value on the server without subscribing. Construct stores with a factory (new Store(() => initialState())) so each request can seed its own state.

Next: React adapter →