Skip to content
cceevvPublic

About

Tiny state manager based on React Hooks, with automatic performance optimization.

Resources

Stars

3 stars

Watchers

1 watching

Forks

Repository files navigation

tinystore

A tiny React state manager with explicit actions, readonly state, and selective subscriptions.

npm GitHub Workflow Status Coverage Status npm bundle size npm type definitions GitHub

English · 简体中文


Why tinystore

tinystore is a React-first store for teams that want a tiny API surface and a hard mutation boundary:

  • State defines data
  • Action is 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.

Features

  • Class-based State + Action model
  • Readonly state snapshots outside actions
  • useStore(selector) for selective subscriptions
  • subscribe(...) for non-React integrations
  • Async actions and function-style updates

Install

pnpm add @cceevv/tinystore
# or
yarn add @cceevv/tinystore
# or
npm i @cceevv/tinystore

Quick start

import 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>
    </>
  );
}

Core ideas

State defines data

StateClass should be a simple class with public fields.

class ProfileState {
  nickname = "";
  age = 0;
}

Action owns mutations

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 }));
  }
}

Components subscribe with selectors

Use selectors by default. useStore() without arguments subscribes to the whole state snapshot.

const age = profileStore.useStore((state) => state.age);
const profile = profileStore.useStore();

API

tinyStore(StateClass, ActionClass)

Creates a store and returns:

  • useStore
  • getStore
  • subscribe
  • actions

useStore()

Returns the full readonly state snapshot.

const state = store.useStore();

useStore(selector, equalityFn?)

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);

getStore()

Returns the current readonly state snapshot. It can be used outside React components.

const snapshot = store.getStore();

subscribe(listener)

Subscribes to the full state. The listener receives (next, prev).

const unsubscribe = store.subscribe((next, prev) => {
  console.log(next, prev);
});

subscribe(selector, listener, equalityFn?)

Subscribes to a selected value outside React.

const unsubscribe = store.subscribe(
  (state) => state.count,
  (next, prev) => {
    console.log(next, prev);
  },
);

actions()

Returns the readonly action map.

const { inc } = store.actions();

Design constraints

  • State snapshots are readonly outside actions.
  • Nested objects should be replaced immutably when updated.
  • tinystore is React-first, not a cross-framework store core.
  • tinystore is not a deep reactive proxy system.

Comparison

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

Migration from v1

  • useStore(true) was removed.
  • getStore(true) was removed.
  • useStore(selector) is now the recommended subscription API.
  • State snapshots are always readonly.

FAQ

Why classes?

To keep state structure and mutation entry points explicit and easy to scan in code review.

Why can't I mutate state directly?

Because tinystore treats explicit actions as a core design constraint, not an optional pattern.

Is tinystore React-only?

The public API is React-first. It also exposes getStore() and subscribe() for non-React integrations.

How is it different from Zustand?

tinystore trades ecosystem breadth for a narrower model with harder mutation boundaries.

License

MIT License (c) cceevv

About

Tiny state manager based on React Hooks, with automatic performance optimization.

Resources

Stars

3 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages