{"id":20720211,"url":"https://github.com/zaaack/nstate","last_synced_at":"2025-04-23T14:27:40.630Z","repository":{"id":65370521,"uuid":"417361205","full_name":"zaaack/nstate","owner":"zaaack","description":"A simple but powerful react state management library with low mind burden","archived":false,"fork":false,"pushed_at":"2025-03-13T02:24:36.000Z","size":200,"stargazers_count":14,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-30T00:31:37.756Z","etag":null,"topics":["class","ddd","ddd-architecture","ddd-patterns","immer","mvc","react","react-state-management","react-store","reactive","redux","simple","state-management"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zaaack.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-10-15T03:50:28.000Z","updated_at":"2025-03-13T02:24:40.000Z","dependencies_parsed_at":"2024-10-21T12:19:11.029Z","dependency_job_id":null,"html_url":"https://github.com/zaaack/nstate","commit_stats":{"total_commits":56,"total_committers":2,"mean_commits":28.0,"dds":0.375,"last_synced_commit":"fd6eef81692857cfba9ffa048405857649942821"},"previous_names":[],"tags_count":36,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zaaack%2Fnstate","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zaaack%2Fnstate/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zaaack%2Fnstate/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zaaack%2Fnstate/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zaaack","download_url":"https://codeload.github.com/zaaack/nstate/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250450891,"owners_count":21432727,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["class","ddd","ddd-architecture","ddd-patterns","immer","mvc","react","react-state-management","react-store","reactive","redux","simple","state-management"],"created_at":"2024-11-17T03:19:39.466Z","updated_at":"2025-04-23T14:27:40.608Z","avatar_url":"https://github.com/zaaack.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# nstate\n\n[![publish](https://github.com/zaaack/nstate/actions/workflows/publish.yml/badge.svg)](https://github.com/zaaack/nstate/actions/workflows/publish.yml) [![npm](https://img.shields.io/npm/v/nstate.svg)](https://www.npmjs.com/package/nstate) [![npm](https://img.shields.io/npm/dm/nstate.svg)](https://www.npmjs.com/package/nstate) [![size](https://badgen.net/bundlephobia/minzip/nstate)](https://bundlephobia.com/package/nstate)\n\nA simple but powerful react state management library with low mental load, inspired by [rko](https://github.com/steveruizok/rko).\n\n\u003e nstate = nano state / next state\n\n## Contents\n\n- [nstate](#nstate)\n  - [Contents](#contents)\n  - [Features](#features)\n  - [Install](#install)\n  - [API](#api)\n  - [Usage](#usage)\n    - [1. Counter example](#1-counter-example)\n    - [2. Bind state field to form input with onChange/value` with type safety](#2-bind-state-field-to-form-input-with-onchangevalue-with-type-safety)\n    - [3. Combine multiple store to reuse actions/views](#3-combine-multiple-store-to-reuse-actionsviews)\n    - [4. useLocalStore](#4-uselocalstore)\n    - [5. useSubStore](#5-usesubstore)\n  - [License](#license)\n\n## Features\n\n* Simple API with low mental load\n* Powerful features based on concise API.\n* Auto bind action methods\n* Combine stores and reuse actions/views\n* Watch store changes\n* Shipped with [immer](https://immerjs.github.io/immer/) for nested state updating\n* Bind state field to form input with value/onChange/defaultValue\n* Flexible, you can customize all internal methods by override.\n\n\n## Install\n\n```sh\nyarn add nstate # or npm i nstate\n```\n\n\n## API\n\n```ts\nexport function setDebug(boolean):void // enable debug log\n\nexport default class NState\u003cT\u003e {\n  protected state\u003cT\u003e\n  protected events: Emitter\u003c{\n    change: { patch: any, old: T }\n  }\u003e // internal change events\n  constructor(initialState: T, nameOrOptions?: string | { name: string, debug: boolean})\n  protected onInit()\n  protected setState(patch: Partial\u003cT\u003e)\n  protected setState(patch: (s: T) =\u003e Partial\u003cT\u003e)\n  protected setState(patch: (draft: T) =\u003e void) // using immer under the hood\n  watch\u003cU\u003e(getter: (s: T) =\u003e U, handler: (s: U) =\u003e void) // Watch deep state change, if getter return a new array(length \u003c= 20) or object, it will be shallow equals\n  unwatch\u003cU\u003e(handler: (s: U) =\u003e void) // remove watch listener\n  useWatch\u003cU\u003e(getter: (s: T) =\u003e U, handler: (s: U) =\u003e void, deps?: any[]) // watch hooks wrapper for auto remove handler after unmount and auto update when deps changes\n  useState\u003cU\u003e(getter: (s: T) =\u003e U): U // use state hook, based on `watch`, so you can return a new array/object for destructuring.\n  useBind\u003cU\u003e(getter: (s: T) =\u003e U): \u003cK extends keyof U\u003e(key: K, transformer?: (v: string) =\u003e U[K]) // bind state field to form input\n  useSubStore\u003cU\u003e(getter: (s: T) =\u003e U, setter(s: T, u: U) =\u003e T) // create sub stores for get/set/watch, it will auto sync state to parent store\n  dispose() // clear all event listeners, for sub stores/local stores\n}\n\nexport function useLocalStore\u003cT, U\u003e(state: T, actions: (store: LocalStore\u003cT\u003e) =\u003e U): [T, LocalStore\u003cT\u003e \u0026 U]\n```\n## Usage\n\n### 1. Counter example\n\n```tsx\nimport NState, { setDebug } from 'nstate'\nimport React from 'react'\n\nsetDebug(true) // enable debug log\n\ninterface Store {\n  count: number\n}\nexport class CounterStore extends NState\u003cStore\u003e {\n\n  inc() {\n    // setState by new state\n    this.setState({ count: this.state.count + 1 })\n  }\n\n  dec() {\n    // setState by updater function like React\n    this.setState(s =\u003e ({ count: s.count - 1 }))\n  }\n\n  set(n: number) {\n    // setState by immer draft (using immer under the hood)\n    this.setState(draft =\u003e {\n      draft.count = n\n    })\n  }\n}\n\nexport const counterStore = new CounterStore({ // optional initial state\n  count: 0,\n})\n\nfunction Counter({ store = counterStore }: { store?: CounterStore }) {\n  const count = store.useState(s =\u003e s.count)\n  const inpRef = React.useRef\u003cHTMLInputElement\u003e(null)\n  return (\n    \u003cdiv\u003e\n      \u003cdiv\u003e\n        \u003ch2\u003eCounter\u003c/h2\u003e\n        \u003cp\u003ecount: {count}\u003c/p\u003e\n        \u003cbutton onClick={store.inc}\u003e+\u003c/button\u003e\n        \u003cbutton onClick={store.dec}\u003e-\u003c/button\u003e\n        \u003cbutton onClick={e=\u003estore.set(0)}\u003ereset\u003c/button\u003e\n      \u003c/div\u003e\n    \u003c/div\u003e\n  )\n}\n\nexport default Counter\n```\n\n\n### 2. Bind state field to form input with onChange/value` with type safety\n\n```tsx\nfunction Counter() {\n  const count = counterStore.useState(s =\u003e s.count)\n  const bind = counterStore.useBind(s =\u003e s) // you can also bind nested object with (s =\u003e s.xx.aa)\n  return (\n    \u003cdiv\u003e\n      count: {count}\n      \u003cinput type=\"text\" {...bind('count', Number)} /\u003e\n    \u003c/div\u003e\n  )\n}\n\n```\n\n### 3. Combine multiple store to reuse actions/views\n\n```tsx\nimport NState, { setDebug } from 'nstate'\nimport React from 'react'\nimport Counter, {CounterStore} from './Counter';\n\nsetDebug(true) // enable debug log\ninterface Store {\n  nest: {\n    aa: string,\n    bb: string,\n  }\n}\nexport class CombineStore extends NState\u003cStore\u003e {\n\n  counter = new CounterStore({ count: 1 })\n\n  onInit() {\n    // link to counter store by simple watch API\n    this.counter.watch(s=\u003e s.count, count =\u003e {\n      this.updateAA('count changed:'+count)\n    })\n  }\n\n  updateAA(aa: string) {\n    this.setState(draft =\u003e {\n      draft.nest.aa = aa\n    })\n  }\n  updateBB(bb: string) {\n    this.setState(draft =\u003e {\n      draft.nest.bb = bb\n    })\n  }\n}\n\nexport const nestStore = new CombineStore({ // initial state\n  nest: {aa: 'aa', bb: 'bb'},\n})\n\nfunction Combine() {\n  // use state by destructuring, support array/object\n  const [aa, bb] = nestStore.useState(s =\u003e [s.nest.aa, s.nest.bb])\n  // or:\n  // const {aa, bb} = nestStore.useState(s =\u003e ({aa: s.nest.aa, bb: s.nest.bb}))\n  const inp1Ref = React.useRef\u003cHTMLInputElement\u003e(null)\n  const inp2Ref = React.useRef\u003cHTMLInputElement\u003e(null)\n  // watch hooks wrapper for auto remove handler after unmount\n  nestStore.useWatch(s =\u003e [s.nest.aa, s.nest.bb], [aa, bb] =\u003e {\n    // do something when state changes\n  })\n  return (\n    \u003cdiv\u003e\n      \u003cdiv\u003e\n        \u003ch2\u003eCombine\u003c/h2\u003e\n        \u003cCounter store={nestStore.counter} /\u003e\n        \u003cp\u003eaa: {aa}\u003c/p\u003e\n        \u003cp\u003ebb: {bb}\u003c/p\u003e\n        \u003cinput ref={inp1Ref} type=\"text\" defaultValue={aa}/\u003e\n        \u003cbutton\n          onClick={e =\u003e {\n            nestStore.updateAA(inp1Ref.current?.value || '')\n          }}\n        \u003e\n          set aa\n        \u003c/button\u003e\n        \u003cinput ref={inp2Ref} type=\"text\" defaultValue={bb}/\u003e\n        \u003cbutton\n          onClick={e =\u003e {\n            nestStore.updateBB(inp2Ref.current?.value || '')\n          }}\n        \u003e\n          set bb\n        \u003c/button\u003e\n      \u003c/div\u003e\n    \u003c/div\u003e\n  )\n}\n\nexport default Combine\n```\n\n### 4. useLocalStore\n\n```tsx\nfunction CounterWithLocalStore() {\n  const [count, store] = useLocalStore(0, store =\u003e ({\n    inc: () =\u003e store.setState(s =\u003e s + 1),\n    dec: () =\u003e store.setState(s =\u003e s - 1),\n  }))\n  return (\n    \u003cdiv\u003e\n      \u003cdiv\u003e\n        \u003ch2\u003eCounter with useLocalStore\u003c/h2\u003e\n        \u003cp\u003ecount: {count}\u003c/p\u003e\n        \u003cbutton onClick={store.inc}\u003e+\u003c/button\u003e\n        \u003cbutton onClick={store.dec}\u003e-\u003c/button\u003e\n        \u003cbutton onClick={e=\u003estore.setState(0)}\u003ereset\u003c/button\u003e\n      \u003c/div\u003e\n    \u003c/div\u003e\n  )\n}\n```\n\n### 5. useSubStore\n\n```tsx\ninterface Store {\n  counter1: {\n    count: number\n  }\n  counter2: {\n    count: number\n  }\n}\nexport class Store extends NState\u003cStore\u003e {\n\n}\nexport const store = new Store({ // initial state\n  counter1: {count: 1},\n  counter2: {count: 1},\n})\n\nfunction SubCounter({ store }) {\n  return (\n    \u003cdiv\u003e\n      \u003cdiv\u003e\n        \u003ch2\u003eCounter with useLocalStore\u003c/h2\u003e\n        \u003cp\u003ecount: {count}\u003c/p\u003e\n        \u003cbutton onClick={store.setState(s =\u003e s.count++)}\u003e+\u003c/button\u003e\n        \u003cbutton onClick={store.setState(s =\u003e s.count--)}\u003e-\u003c/button\u003e\n        \u003cbutton onClick={e=\u003estore.setState(s =\u003e {\n          s.count = 0\n        })}\u003ereset\u003c/button\u003e\n      \u003c/div\u003e\n    \u003c/div\u003e\n  )\n}\nfunction Counter() {\n  const subStore = store.useSubStore(s =\u003e s.counter1, (s, u) =\u003e { s.counter1 = u })\n  return \u003cSubCounter store={subStore} /\u003e\n}\n```\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzaaack%2Fnstate","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzaaack%2Fnstate","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzaaack%2Fnstate/lists"}