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