DEV Community

Cover image for React Tiny Store
anirudhchintha95
anirudhchintha95

Posted on

React Tiny Store

I released @acoolhq/react-tiny-store. It is a small external store that works with useSyncExternalStore, is hydration-safe, and keeps re-renders scoped with selectors.

Install

npm i @acoolhq/react-tiny-store
Enter fullscreen mode Exit fullscreen mode

Why I built a tiny store

I needed something between raw Context and a full-blown state library. Context + reducers made unrelated components re-render whenever a big object changed, and pulling in Redux/Zustand felt like overkill for a few shared slices.

When it helps

  • You want fine-grained renders without heavy setup.
  • You have a few shared slices and don’t want a large framework.
  • You care about SSR correctness with minimal API surface.

What it isn’t

  • A full-featured global state framework with time-travel and middleware. It’s a small, practical layer for the 80% case.

Why use it

  • Selector-based reads so only affected components re-render
  • Works with SSR hydration
  • Small API with pure slice actions and optional controller actions
import { createContextSync } from "@acoolhq/react-tiny-store";

type Todo = { id: string; text: string; optimistic?: boolean };
type AppState = { todos: Todo[]; lastUpdated: number };

const TodosContext = createContextSync<AppState>();
const { Provider, useSelector, bindActions, createSlice } = TodosContext;

const useTodos = createSlice((root) => root.todos, {
  add(root, todo: Todo) {
    return { ...root, todos: [todo, ...root.todos] };
  },
});

const useTodosActions = bindActions((api) => {
  const { actions: todos } = useTodos();
  return {
    async addAndPersist(text: string) {
        try {
          const res = await fetch("/api/todos", {
            method: "POST",
            body: JSON.stringify({ text }),
          });
          const real = await res.json();
          todos.add(real);
        } catch {
          // handle error
        }
      },
  };
});

export function App() {
  return (
    <Provider initial={{ todos: [], lastUpdated: 0 }}>
      <TodosScreen />
    </Provider>
  );
}
Enter fullscreen mode Exit fullscreen mode

When to batch

If you do many quick updates back to back (like benchmarks), call batch so subscribers are notified once.

batch keeps state mutations synchronous but delays subscriber notifications, reducing repeated notifications rather than implying React wouldn’t already batch renders.

import { batch } from "@acoolhq/react-tiny-store/batch";

batch(() => {
  store.setState(p => ({ ...p, a: p.a + 1 }));
  store.setState(p => ({ ...p, b: p.b + 1 }));
});
Enter fullscreen mode Exit fullscreen mode

Benchmarks

Live demo: https://acoolhq.github.io/rts-bench/

Links and documentation

NPM: https://www.npmjs.com/package/@acoolhq/react-tiny-store
Github: https://github.com/acoolhq/react-tiny-store
Documentation: https://acoolhq.github.io/react-tiny-store/

If you want more details and a deep dive, check the docs and my repo. Feedback welcome.

Top comments (0)