@feiyeyuji/simple-nobody-state
v0.1.0
Published
A tiny Zustand-powered state toolkit with DI providers, local stores, memoized selectors, batching, and strong TypeScript inference.
Downloads
32
Maintainers
Readme
simple-nobody-state
simple-nobody-state is a small TypeScript-first state toolkit built on top of Zustand. It adds global singleton stores, provider-scoped dependency injection stores, component-local stores, memoized selectors, batch dispatching, strict shallow freezing, and typed helpers for composing and mixing stores.
Install
npm install simple-nobody-state zustand proxy-memoizeReact is a peer dependency and should be >=18.
Global Store
import { createStore } from "simple-nobody-state";
export const counterStore = createStore(
() => ({
state: { count: 0 },
meta: { name: "counter" },
selectors: {
double: (state: { count: number }) => state.count * 2,
},
actions: {
inc: (ctx, amount: number = 1) => {
ctx.setState((state: { count: number }) => ({
count: state.count + amount,
}));
},
},
}),
{
storeType: "globalStore",
batch: true,
strict: true,
devtools: true,
},
);
counterStore.dispatch("inc", 2);
counterStore.actions.inc(1);
counterStore.subscribe((state) => console.log(state.count));In React:
function Counter() {
const count = counterStore.useState((state) => state.count);
const actions = counterStore.useActions();
return <button onClick={() => actions.inc()}>{count}</button>;
}Global stores expose:
- Hooks:
useState,useStateRef,useActions - Getters:
state,selectors,meta,actions - Methods:
getState,dispatch,batchDispatch,subscribe,destroy - Raw Zustand instance:
store[ZUSTAND]
Provider Store
import { createStore, stateEffect } from "simple-nobody-state";
const userStore = createStore(
() => ({
state: { name: "Ada" },
actions: {
rename: (ctx, name: string) => ctx.setState({ name }),
},
hooks: {
created: (store) => console.log("created", store.meta),
destroy: () => console.log("destroyed"),
},
}),
{ storeType: "providerStore" },
);
function User() {
const name = userStore.useState((state) => state.name);
const actions = userStore.useActions();
return <button onClick={() => actions.rename("Grace")}>{name}</button>;
}
function App() {
return (
<userStore.Provider
storeKey="profile"
effect={stateEffect({ name: "Ada" })}
>
<User />
</userStore.Provider>
);
}Provider stores expose Provider, useState, useStateRef, useActions, useInstance, useLocker, and getInstance.
The storeKey controls sharing. Providers with the same key share one instance. Providers without a key create isolated instances. useLocker(storeKey) keeps a keyed instance alive after its Provider unmounts until the locker unmounts.
stateEffect
stateEffect is exported at the top level. It turns a partial state object into a Provider effect tuple:
const effect = stateEffect({ count: 1 });
// [Object.entries({ count: 1 }).flat(), updater]Provider runs the updater inside useEffect with dependencies derived from the flattened tuple plus storeKey, so external props can synchronize into internal store state.
Component Store
useStore creates a component-local store. It is bound to the component lifecycle and is destroyed on unmount.
import { useStore } from "simple-nobody-state";
function LocalCounter() {
const store = useStore(() => ({
state: { count: 0 },
actions: {
inc: (ctx) =>
ctx.setState((state: { count: number }) => ({
count: state.count + 1,
})),
},
}));
const count = store.useState((state) => state.count);
const actions = store.useActions();
return <button onClick={() => actions.inc()}>{count}</button>;
}There is no useLocal alias.
Mixin And Compose
mixin combines definition factories and rejects duplicate state/action/selector keys. Metadata is shallow-merged and lifecycle hooks run in order.
const mixed = mixin(sliceA, sliceB);
const store = createStore(mixed, { storeType: "globalStore" });composeStore groups global and provider stores behind one ComposeProvider and namespace-based hooks.
const composed = composeStore({ counter: counterStore, user: userStore });
function Page() {
const count = composed.useComposeState("counter", (state) => state.count);
const userActions = composed.useComposeActions("user");
return <button onClick={() => userActions.rename("Grace")}>{count}</button>;
}
function App() {
return (
<composed.ComposeProvider providers={{ user: { storeKey: "user" } }}>
<Page />
</composed.ComposeProvider>
);
}Scripts
npm run typecheck
npm run build
npm test