Kabinet is a minimal framework for (p)react based webapps that provides an external state designed to be used together with with React's useEffect and useSyncExternalStore.
See Subscribing to an external store in the React manual for a better explanation why this is useful.
From version 1.x the API is strictly typescript and compiled to ES6.
npm install kabinet --save-dev
This example draws from the useSyncExternalStore example.
count.ts
import Store from "kabinet";
import { useSyncExternalStore } from 'react';
type CountState = { count: number };
class CountStore extends Store<CountState>{
increment() {
this.setState({ count: this.state.count + 1 });
}
};
export const countStore = new CountStore({ count: 0 });
export function useCountStore() {
return useSyncExternalStore(
subscribe => countStore.observe(subscribe),
() => countStore.getState()
);
}
App.tsx
import { useCountStore, countStore } from './count';
function App() {
const {count} = useCountStore();
return (
<button onClick={() => countStore.increment()}>
count is {count}
</button>
)
}
export default App
Since reading about the flux architecture pattern I've had copies of something like store.js around in my projects. Kabinet is where I publish my latest copy. Since my teams have moved to typescript, I dropped runtime type systems and have simplified this library significantly.
kabinet is simple, uses no globals, and can be made to work well with server-side rendering.
Examples above used singletons, which which should be avoided (or used with care!) when rendering server-side.
One simple solution is is to keep track of all singletons and make sure to clean and swap them out before each render using a "store keeper".
See ./src/keeper/ for a small proof of concept:
server.ts
import { setStore, clearStores } from "kabinet/dist/keeper";
import { CountStore } from "./client/stores/count";
const render = (App, count=0) => {
clearStores();
setStore(CountStore, new CountStore({count}));
return ReactDOMServer.renderToSTring(<App />);
}
App.tsx
import { CountStore } from './count';
import { getStore } from "kabinet/dist/keeper";
const countStore = getStore(CountStore);
const useCountStore = () => {
return useSyncExternalStore(
subscribe => countStore.observe(subscribe),
() => countStore.getState()
);
}
function App() {
const {count} = useCountStore();
return (
<button onClick={() => countStore.increment()}>
count is {count}
</button>
)
}
export default App
Prepare and run the demo using npm run demo
.