UNPKG

@qundus/qstate

Version:
322 lines (253 loc) 10.6 kB
<!-- vars --> [store]: https://github.com/nanostores/nanostores <!-- intro --> <br /> <img width="150px" height="50px" src="https://img.shields.io/badge/intro-blue?style=for-the-badge" /> <br /> [nanostores][store] is such a great state management library but some flows get annoying and repeatetively redundant when used with framework specific hooks for example. this library aims to provide a nicer interface to all of nanostores and hopfully reach it's full potential how i see it. <!-- features --> <br /> <img width="150px" height="50px" src="https://img.shields.io/badge/features-yellow?style=for-the-badge" /> <br /> <!-- - Drop in replacement for nanostores. --> - Make your own mini state management through setups. - Addons to speedup certain tasks without writing and importing so much. - hooks included, you just need to install seperately. - events included out of the box. - Fully Typescript support - ESM only (for now at least) <!-- disclaimer --> <br /> <img width="150px" height="50px" src="https://img.shields.io/badge/disclaimer-red?style=for-the-badge" /> <br /> For now this is just a play on how nanostores and it's tools should be used according to me, there's no contributions to nanostores or any other library used in this project so far, the moment i do some i'll list them here, and thanks to open-source collective. <!-- installation --> <br /> <img width="150px" height="50px" src="https://img.shields.io/badge/installation-gray?style=for-the-badge" /> <br /> ```shell pnpm add @qundus/qstate ``` tsconfig.json ```json { "compilerOptions": { "preserveSymlinks": true } } ``` <!-- basic usage --> <br /> <img width="150px" height="50px" src="https://img.shields.io/badge/basics-gray?style=for-the-badge" /> <br /> ```ts // or map, persistent or deepMap import { atom } from "@qundus/qstate"; const state = atom(false); ``` <!-- basics: events --> <br /> <img width="150px" src="https://img.shields.io/badge/basics-events-blue?style=for-the-badge" /> <br /> ```ts import { onChangeEvent, onMountEvent } from "@qundus/qstate/events"; const state = atom(false, { events: { mount: onMountEvent, change: onChangeEvent, } }); // then you can use it anywhere else // nanostores has preserved the events key so i chose 'on' state.on.mount({ state, serverSide }) { // can be async as well if (serverSide) { // do things on server only or // return // abort everything on server-side } // state.set(true) // set only on client side }; // then you can use it anywhere else state.on.change({ $next }) => { // TODO: do something on change, $next.newValue is mutable } ``` <!-- basics: hooks --> <br /> <img width="150px" src="https://img.shields.io/badge/basics-hooks-blue?style=for-the-badge" /> <br /> hooks rely on ease of access and facilitates easy transition between projects when it comes to global states by offering a single `hooks` plugin that gathers all possible frameworks and vanilla in one place instead of the extremely tedios `useStore` imported from every independent framework hook: ```ts // treeshakable and stanalone imports to avoid framework specific imports import { atom } from "@qundus/qstate"; import { solidHook, solidHookUnwrapped } from "@qundus/qstate/solid"; import { preactHook } from "@qundus/qstate/preact"; // and so on... const state = atom(false, { // hooks are directly linked to the state // this is just a record, you can name the hook whatever you want hooks: { usePreact: preactHook, // pnpm add @nanostores/preact reactHook, // pnpm add @nanostores/react solid: solidHook, // pnpm add @nanostores/solid solidBare: solidHookUnwrapped, // same for solid solidFrom: solidHookFrom, // pnpm add solid-js solidFromBare: solidHookFromUnwrapped, // same as soliFromHook svelteHook, // pnpm add svelte useVue: vueHook, // pnpm add @nanostores/vue html: vanillaHook, // nothing, vanilla hook is here for consistency ;) }, }); ``` next is how we use the hooks, below is an example that can be implemented in any framework in the proper areas where hooks are usually used: ```ts import { state } from "wherever"; // somewhere in your component const $reactive = state.hooks.usePreact() const $reactive = state.hooks.html() // and so on... ``` so if any transition needed out of the current framework, global state isn't an issue anymore, just replace current framework hook with the next one: ```ts import { atom } from "@qundus/qstate"; import { solidHookUnwrapped } from "@qundus/qstate/solid"; // let's say you made this const state = atom(false, { hooks: { useStore: solidHookUnwrapped, // replace this with any framework hook and voila }, }); ``` and now you hate solid for some crazy reason or just want to switch, all you need to do is replace `solidHookUnwrapped` with the next framework hooks like `preactHook`, nice right :) <!-- basics: addons --> <br /> <img width="150px" src="https://img.shields.io/badge/basics-addons-blue?style=for-the-badge" /> <br /> addons offer an interface to add on demand functionality to any store, for example, the `updateAddon` adds a function to the store to update the previous state wtihout having to do the annoying get and set everytime, plus it guarantees updates for objects. And so, currently there's only few addons but i plan to add more since it's too easy, this is how to use them: ```ts import { map } from "@qundus/qstate"; import { checksAddon, deriveAddon, updateAddon } from "@qundus/qstate/addons"; const $state = map({ qstate: "awesome" }, { addons: { update: updateAddon, checks: checksAddon, derive: deriveAddon, // no more extra imports, just derive from this state }, }); // somewhere in your code const $derived = $state.derive((value) => { return { ahaa: "gotteemmm" }; }); ``` <!-- setups --> <br /> <img width="150px" height="50px" src="https://img.shields.io/badge/setups-gray?style=for-the-badge" /> <br /> It's definitely nice to be able to do all this from one place in your app or library, but this can get quickly annoying if you try to use more than two or three stores, this is where setups come to rescue: ```ts // file: setups/state/index.ts // or setupAtomPersistent, setupMap or setupDeepMap import { atomSetup } from "@qundus/qstate"; import { solidHook } from "@qundus/qstate/solid"; import { onChangeEvent, onChangeEventSetup } from "@qundus/qstate/events"; // note: keys are going to be merged with created states from this atom export const atom = setupAtom({ addons: { derive: deriveAddon, }, hooks: { solid: solidHook, }, events: { onchange: onChangeEventSetup(({$next}) => { // listen to and modify all the stores in your app, dayum! }) } }); // // file: anywhere in your app import { atom } from ":setups/state"; // now you have all the options pre-recorded by the setup. const $state = atom(false) // merged addons already here // create events that would be monitored by parent $state.on.onchange(() => { console.log('changed wohoo!'); }); // now use it, everything is preserved through merged options. const $reactive = $state.hooks.solid() ``` <!-- integrations --> <br /> <img width="150px" height="50px" src="https://img.shields.io/badge/integrations-gray?style=for-the-badge" /> <br /> some framework integrations are necessary in some cases to avoid faulty store behavior, and those are: <br /> <img width="150px" src="https://img.shields.io/badge/integrations-astro-blue?style=for-the-badge" /> <br /> ```shell pnpm add astro @inox-tools/request-nanostores ``` usage: ```ts // file: astro.config.ts | .mjs .js import { qStateAstroIntegration } from "@qundus/qstate/astro"; // https://astro.build/config export default defineConfig({ integrations: [ qStateAstroIntegration(); ] }); // ----- // file: src/middleware/index.ts import { qStateAstroMiddleware } from '@qundus/qstate/astro'; const global: MiddlewareHandler = async (ctx, next) => { return next(); }; export const onRequest = sequence( // keep comment for better formatting // i18nMiddleware, // in development, stay tuned :) global, // keep right before state middleware qStateAstroMiddleware // keep last ); ``` that's all for now, any suggestions or ideas you can open an issue and i'll be happy to look at them, stay safe. <!-- issues --> <br /> <img width="150px" src="https://img.shields.io/badge/issues-gray?style=for-the-badge" /> <br /> <br /> <img width="150px" height="50px" src="https://img.shields.io/badge/issues-astro-blue?style=for-the-badge" /> <br /> ```md Unable to render ReactLogo! This component likely uses @astrojs/react, @astrojs/preact, @astrojs/solid-js, @astrojs/vue or @astrojs/svelte, but Astro encountered an error during server-side rendering. this is caused by using a key on the store that doesn't exist like hooks, so double check that ``` there are several causes for this: - check if the respective framework hook is installed like @nanostores/preact - check if addons/hooks that are not used or added are being accessed - if using bun runtime, check if the addons/hooks are passed to children of the state properly, children like if you used derive addon and used it, check if the derived state has the same hooks or not <!-- roadmap --> <br /> <img width="150px" height="50px" src="https://img.shields.io/badge/roadmap-gray?style=for-the-badge" /> <br /> - [x] change events optios to follow same way as addons and hooks, make it a plugin - [-] add the rest of events like onNotify and onStart; (for now it's direct port from nanostores) - limit onchangesetup function call to one per state - find a way to call events without the need for it be used by the state's events - [ ] give the ability to make custom addons, hooks, events on the fly - [ ] fix some nextjs server/client side issues where states throw inexplicaple errors - [x] offer standard addons interface - [x] finish astrojs integration tooling - [x] fix events key name conflict with nanostores - [ ] finish astrojs feature docs with addon, actions, and methods <!-- refs --> <br /> <img width="200px" height="50px" src="https://img.shields.io/badge/references_&_thanks_to-grey?style=for-the-badge" /> <br /> - [nanostores][store] - [nanostores preact](https://www.npmjs.com/package/@nanostores/preact) - [nanostores react](https://www.npmjs.com/package/@nanostores/react) - [nanostores solid](https://www.npmjs.com/package/@nanostores/solid) - [nanostores vue](https://www.npmjs.com/package/@nanostores/vue) - [request nanostores](https://github.com/Fryuni/inox-tools/tree/main/packages/request-nanostores)