@qundus/qstate
Version:
your everyday state pal
322 lines (253 loc) • 10.6 kB
Markdown
<!-- 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)