{"id":13727162,"url":"https://github.com/byte-fe/react-model","last_synced_at":"2026-01-23T20:01:19.777Z","repository":{"id":33720496,"uuid":"160473467","full_name":"byte-fe/react-model","owner":"byte-fe","description":"The next generation state management library for React","archived":false,"fork":false,"pushed_at":"2023-04-23T10:05:41.000Z","size":1360,"stargazers_count":236,"open_issues_count":19,"forks_count":21,"subscribers_count":7,"default_branch":"main","last_synced_at":"2025-12-04T22:18:19.686Z","etag":null,"topics":["hooks","middleware","react","redux","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":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/byte-fe.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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}},"created_at":"2018-12-05T06:46:43.000Z","updated_at":"2024-07-26T08:28:40.000Z","dependencies_parsed_at":"2024-02-04T19:44:04.262Z","dependency_job_id":null,"html_url":"https://github.com/byte-fe/react-model","commit_stats":{"total_commits":227,"total_committers":8,"mean_commits":28.375,"dds":0.1806167400881057,"last_synced_commit":"e7126efec5cf8fc858ecc868d30dede4a231bff0"},"previous_names":[],"tags_count":35,"template":false,"template_full_name":null,"purl":"pkg:github/byte-fe/react-model","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/byte-fe%2Freact-model","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/byte-fe%2Freact-model/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/byte-fe%2Freact-model/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/byte-fe%2Freact-model/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/byte-fe","download_url":"https://codeload.github.com/byte-fe/react-model/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/byte-fe%2Freact-model/sbom","scorecard":{"id":260177,"data":{"date":"2025-08-11","repo":{"name":"github.com/byte-fe/react-model","commit":"e9dfb94cc71d23919ab3152e4738efc68414fc98"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.6,"checks":[{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Code-Review","score":2,"reason":"Found 5/23 approved changesets -- score normalized to 2","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/node.js.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/node.js.yml:23: update your workflow using https://app.stepsecurity.io/secureworkflow/byte-fe/react-model/node.js.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/node.js.yml:25: update your workflow using https://app.stepsecurity.io/secureworkflow/byte-fe/react-model/node.js.yml/main?enable=pin","Warn: downloadThenRun not pinned by hash: .github/workflows/node.js.yml:34","Info:   0 out of   2 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   1 downloadThenRun dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":0,"reason":"license file not detected","details":["Warn: project does not have a license file"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 28 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":0,"reason":"24 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-968p-4wvh-cqc8","Warn: Project is vulnerable to: GHSA-67hx-6x53-jw92","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-w573-4hg7-7wgq","Warn: Project is vulnerable to: GHSA-fjxv-7rqg-78g4","Warn: Project is vulnerable to: GHSA-9c47-m6qq-7p4h","Warn: Project is vulnerable to: GHSA-76p3-8jx3-jpfq","Warn: Project is vulnerable to: GHSA-3rfm-jhwj-7488","Warn: Project is vulnerable to: GHSA-hhq3-ff78-jv3g","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-5fw9-fq32-wv5p","Warn: Project is vulnerable to: GHSA-rp65-9cf3-cjxr","Warn: Project is vulnerable to: GHSA-566m-qj78-rww5","Warn: Project is vulnerable to: GHSA-7fh5-64p2-3v2j","Warn: Project is vulnerable to: GHSA-p8p7-x288-28g6","Warn: Project is vulnerable to: GHSA-gcx4-mw62-g8wm","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-4wf5-vphf-c2xc","Warn: Project is vulnerable to: GHSA-52f5-9888-hmc6","Warn: Project is vulnerable to: GHSA-72xf-g2v4-qvf3","Warn: Project is vulnerable to: GHSA-j8xg-fqg3-53r7","Warn: Project is vulnerable to: GHSA-3h5v-q93c-6h6q"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-17T10:39:41.534Z","repository_id":33720496,"created_at":"2025-08-17T10:39:41.534Z","updated_at":"2025-08-17T10:39:41.534Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28699372,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-23T17:25:48.045Z","status":"ssl_error","status_checked_at":"2026-01-23T17:25:47.153Z","response_time":59,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["hooks","middleware","react","redux","state-management"],"created_at":"2024-08-03T01:03:42.265Z","updated_at":"2026-01-23T20:01:19.740Z","avatar_url":"https://github.com/byte-fe.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"# react-model \u0026middot; ![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg) [![npm version](https://img.shields.io/npm/v/react-model.svg?style=flat)](https://www.npmjs.com/package/react-model) [![minified size](https://badgen.net/bundlephobia/min/react)](https://bundlephobia.com/result?p=react-model) [![Node.js CI](https://github.com/byte-fe/react-model/actions/workflows/node.js.yml/badge.svg?branch=main)](https://github.com/byte-fe/react-model/actions/workflows/node.js.yml) [![size](http://img.badgesize.io/https://cdn.jsdelivr.net/npm/react-model/dist/react-model.js?compression=gzip)](http://img.badgesize.io/https://cdn.jsdelivr.net/npm/react-model/dist/react-model.js) [![downloads](https://img.shields.io/npm/dt/react-model.svg)](https://www.npmjs.com/package/react-model) [![Coverage Status](https://codecov.io/gh/byte-fe/react-model/branch/master/graph/badge.svg)](https://codecov.io/gh/byte-fe/react-model) [![Greenkeeper badge](https://badges.greenkeeper.io/byte-fe/react-model.svg)](https://greenkeeper.io/) ![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)\n\nThe State management library for React\n\n🎉 Support Both Class and Hooks Api\n\n⚛️ Support [preact](https://github.com/byte-fe/react-model-experiment/tree/preact), react-native and Next.js\n\n⚔ Full TypeScript Support\n\n📦 Built with microbundle\n\n⚙️ Middleware Pipline ( redux-devtools support ... )\n\n☂️ 100% test coverage, safe on production\n\n🐛 Debug easily on test environment\n\n```tsx\nimport { useModel, createStore } from 'react-model'\n\n// define model\nconst useTodo = () =\u003e {\n  const [items, setItems] = useModel(['Install react-model', 'Read github docs', 'Build App'])\n  return { items, setItems }\n}\n\n// Model Register\nconst { useStore } = createStore(useTodo)\n\nconst App = () =\u003e {\n  return \u003cTodoList /\u003e\n}\n\nconst TodoList = () =\u003e {\n  const { items, setItems } = useStore()\n  return \u003cdiv\u003e\n    \u003cAddon handler={setItems} /\u003e\n    {state.items.map((item, index) =\u003e (\u003cTodo key={index} item={item} /\u003e))}\n  \u003c/div\u003e\n}\n```\n\n---\n\n## Recently Updated\n\n* [feat(middleware): support enable/disable sepecific middleware](#how-can-i-disable-the-console-debugger)\n* fix(stateupdater): fix the issue that setState on unmounted component\n\n## Quick Start\n\n[createStore + useModel](https://codesandbox.io/s/createstore-usemodal-all-of-your-state-4u8s6)\n\n[CodeSandbox: TodoMVC](https://codesandbox.io/s/moyxon99jx)\n\n[Next.js + react-model work around](https://github.com/byte-fe/react-model-experiment)\n\n[v2 docs](https://github.com/byte-fe/react-model/blob/v2/README.md)\n\ninstall package\n\n```shell\nnpm install react-model\n```\n\n## Table of Contents\n\n- [Core Concept](#core-concept)\n  - [createStore](#createstore)\n  - [Model](#model)\n  - [Model Register](#model-register)\n  - [useStore](#usestore)\n  - [getState](#getstate)\n  - [actions](#actions)\n  - [subscribe](#subscribe)\n- [Advance Concept](#advance-concept)\n  - [immutable Actions](#immutable-actions)\n  - [SSR with Next.js](#ssr-with-nextjs)\n  - [Middleware](#middleware)\n  - [Expand Context](#expand-context)\n- [Other Concept required by Class Component](#other-concept-required-by-class-component)\n  - [Provider](#provider)\n  - [connect](#connect)\n- [FAQ](#faq)\n  - [Migrate from 4.0.x to 4.1.x](#migrate-from-40x-to-41x)\n  - [Migrate from 3.1.x to 4.x.x](#migrate-from-31x-to-4xx)\n  - [How can I disable the console debugger?](#how-can-i-disable-the-console-debugger)\n  - [How can I add custom middleware](#how-can-i-add-custom-middleware)\n    - [How can I make persist models](#how-can-i-make-persist-models)\n  - [How can I deal with local state](#how-can-i-deal-with-local-state)\n  - [How can I deal with huge dataset / circular dataset](#how-can-i-deal-with-huge-dataset--circular-dataset)\n  - [actions throw error from immer.module.js](#actions-throw-error-from-immermodulejs)\n  - [How can I customize each model's middlewares?](#how-can-i-customize-each-models-middlewares)\n\n## Core Concept\n\n### createStore\n\nYou can create a shared / local store by createStore api.\n\n[Online Demo](https://codesandbox.io/s/createstore-usemodal-all-of-your-state-4u8s6)\n\n`model/counter.ts`\n\n```typescript\nimport { useState } from 'react'\nimport { useModel } from 'react-model'\nconst { useStore } = createStore(() =\u003e {\n  const [localCount, setLocalCount] = useState(1) // Local State, Independent in different components\n  const [count, setCount] = useModel(1) // Global State, the value is the same in different components\n  const incLocal = () =\u003e {\n    setLocalCount(localCount + 1)\n  }\n  const inc = () =\u003e {\n    setCount(c =\u003e c + 1)\n  }\n  return { count, localCount, incLocal, inc }\n})\n\nexport default useStore\n```\n\n`page/counter-1.tsx`\n\n```tsx\nimport useSharedCounter from 'models/global-counter'\nconst Page = () =\u003e {\n  const { count, localCount, inc, incLocal } = useStore()\n  return \u003cdiv\u003e\n    \u003cspan\u003ecount: { count }\u003c/span\u003e\n    \u003cspan\u003elocalCount: { localCount }\u003c/span\u003e\n    \u003cbutton onClick={inc}\u003einc\u003c/button\u003e\n    \u003cbutton onClick={incLocal}\u003eincLocal\u003c/button\u003e\n  \u003c/div\u003e\n}\n```\n\n### Model\n\nEvery model has its own state and actions.\n\n```typescript\nconst initialState = {\n  counter: 0,\n  light: false,\n  response: {}\n}\n\ninterface StateType {\n  counter: number\n  light: boolean\n  response: {\n    code?: number\n    message?: string\n  }\n}\n\ninterface ActionsParamType {\n  increment: number\n  openLight: undefined\n  get: undefined\n} // You only need to tag the type of params here !\n\nconst model: ModelType\u003cStateType, ActionsParamType\u003e = {\n  actions: {\n    increment: async (payload, { state }) =\u003e {\n      return {\n        counter: state.counter + (payload || 1)\n      }\n    },\n    openLight: async (_, { state, actions }) =\u003e {\n      await actions.increment(1) // You can use other actions within the model\n      await actions.get() // support async functions (block actions)\n      actions.get()\n      await actions.increment(1) // + 1\n      await actions.increment(1) // + 2\n      await actions.increment(1) // + 3 as expected !\n      return { light: !state.light }\n    },\n    get: async () =\u003e {\n      await new Promise((resolve, reject) =\u003e\n        setTimeout(() =\u003e {\n          resolve()\n        }, 3000)\n      )\n      return {\n        response: {\n          code: 200,\n          message: `${new Date().toLocaleString()} open light success`\n        }\n      }\n    }\n  },\n  state: initialState\n}\n\nexport default model\n\n// You can use these types when use Class Components.\n// type ConsumerActionsType = getConsumerActionsType\u003ctypeof Model.actions\u003e\n// type ConsumerType = { actions: ConsumerActionsType; state: StateType }\n// type ActionType = ConsumerActionsType\n// export { ConsumerType, StateType, ActionType }\n```\n\n[⇧ back to top](#table-of-contents)\n\n### Model Register\n\nreact-model keeps the application state and actions in separate private stores. So you need to register them if you want to use them as the public models.\n\n`model/index.ts`\n\n```typescript\nimport { Model } from 'react-model'\nimport Home from '../model/home'\nimport Shared from '../model/shared'\n\nconst models = { Home, Shared }\n\nexport const { getInitialState, useStore, getState, actions, subscribe, unsubscribe } = Model(models)\n```\n\n[⇧ back to top](#table-of-contents)\n\n### useStore\n\nThe functional component in React ^16.8.0 can use Hooks to connect the global store.\nThe actions returned from useStore can invoke dom changes.\n\nThe execution of actions returned by useStore will invoke the rerender of current component first.\n\nIt's the only difference between the actions returned by useStore and actions now.\n\n```tsx\nimport React from 'react'\nimport { useStore } from '../index'\n\n// CSR\nexport default () =\u003e {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    \u003cdiv\u003e\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      \u003cbutton onClick={e =\u003e actions.increment(33)}\u003ehome increment\u003c/button\u003e\n      \u003cbutton onClick={e =\u003e sharedActions.increment(20)}\u003e\n        shared increment\n      \u003c/button\u003e\n      \u003cbutton onClick={e =\u003e actions.get()}\u003efake request\u003c/button\u003e\n      \u003cbutton onClick={e =\u003e actions.openLight()}\u003efake nested call\u003c/button\u003e\n    \u003c/div\u003e\n  )\n}\n```\n\noptional solution on huge dataset (example: TodoList(10000+ Todos)):\n\n1. use useStore on the subComponents which need it.\n2. use useStore selector. (version \u003e= v4.0.0-rc.0)\n\n[advance example with 1000 todo items](https://codesandbox.io/s/react-model-v4-todomvc-oxyij)\n\n[⇧ back to top](#table-of-contents)\n\n### getState\n\nKey Point: [State variable not updating in useEffect callback](https://github.com/facebook/react/issues/14066)\n\nTo solve it, we provide a way to get the current state of model: getState\n\nNote: the getState method cannot invoke the dom changes automatically by itself.\n\n\u003e Hint: The state returned should only be used as readonly\n\n```jsx\nimport { useStore, getState } from '../model/index'\n\nconst BasicHook = () =\u003e {\n  const [state, actions] = useStore('Counter')\n  useEffect(() =\u003e {\n    console.log('some mounted actions from BasicHooks')\n    return () =\u003e\n      console.log(\n        `Basic Hooks unmounted, current Counter state: ${JSON.stringify(\n          getState('Counter')\n        )}`\n      )\n  }, [])\n  return (\n    \u003c\u003e\n      \u003cdiv\u003estate: {JSON.stringify(state)}\u003c/div\u003e\n    \u003c/\u003e\n  )\n}\n```\n\n[⇧ back to top](#table-of-contents)\n\n### actions\n\nYou can call other models' actions with actions api\n\nactions can be used in both class components and functional components.\n\n```js\nimport { actions } from './index'\n\nconst model = {\n  state: {},\n  actions: {\n    crossModelCall: () =\u003e {\n      actions.Shared.changeTheme('dark')\n      actions.Counter.increment(9)\n    }\n  }\n}\n\nexport default model\n```\n\n[⇧ back to top](#table-of-contents)\n\n### subscribe\n\nsubscribe(storeName, actions, callback) run the callback when the specific actions executed.\n\n```typescript\nimport { subscribe, unsubscribe } from './index'\n\nconst callback = () =\u003e {\n  const user = getState('User')\n  localStorage.setItem('user_id', user.id)\n}\n\n// subscribe action\nsubscribe('User', 'login', callback)\n// subscribe actions\nsubscribe('User', ['login', 'logout'], callback)\n// unsubscribe the observer of some actions\nunsubscribe('User', 'login') // only logout will run callback now\n```\n\n[⇧ back to top](#table-of-contents)\n\n## Advance Concept\n\n### immutable Actions\n\nThe actions use [immer](https://github.com/mweststrate/immer) produce API to modify the Store. You can return a producer in action.\n\nUsing function as return value can make your code cleaner when you modify the deep nested value.\n\nTypeScript Example\n\n```ts\n// StateType and ActionsParamType definition\n// ...\n\nconst model: ModelType\u003cStateType, ActionsParamType\u003e = {\n  actions: {\n    increment: async (params, { state: s }) =\u003e {\n      // return (state: typeof s) =\u003e { // TypeScript \u003c 3.9\n      return state =\u003e {\n        state.counter += params || 1\n      }\n    },\n    decrease: params =\u003e s =\u003e {\n      s.counter += params || 1\n    }\n  }\n}\n\nexport default model\n```\n\nJavaScript Example\n\n```js\nconst Model = {\n  actions: {\n    increment: async (params) =\u003e {\n      return state =\u003e {\n        state.counter += params || 1\n      }\n    }\n  }\n}\n```\n\n[⇧ back to top](#table-of-contents)\n\n### SSR with Next.js\n\n\u003cdetails\u003e\n\u003csummary\u003eStore: shared.ts\u003c/summary\u003e\n\u003cp\u003e\n\n```ts\nconst initialState = {\n  counter: 0\n}\n\nconst model: ModelType\u003cStateType, ActionsParamType\u003e = {\n  actions: {\n    increment: (params, { state }) =\u003e {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    }\n  },\n  // Provide for SSR\n  asyncState: async context =\u003e {\n    await waitFor(4000)\n    return { counter: 500 }\n  },\n  state: initialState\n}\n\nexport default model\n```\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eGlobal Config: _app.tsx\u003c/summary\u003e\n\u003cp\u003e\n\n\n```tsx\nimport { models, getInitialState, Models } from '../model/index'\n\nlet persistModel: any\n\ninterface ModelsProps {\n  initialModels: Models\n  persistModel: Models\n}\n\nconst MyApp = (props: ModelsProps) =\u003e {\n  if ((process as any).browser) {\n    // First come in: initialModels\n    // After that: persistModel\n    persistModel = props.persistModel || Model(models, props.initialModels)\n  }\n  const { Component, pageProps, router } = props\n  return (\n    \u003cContainer\u003e\n      \u003cComponent {...pageProps} /\u003e\n    \u003c/Container\u003e\n  )\n}\n\nMyApp.getInitialProps = async (context: NextAppContext) =\u003e {\n  if (!(process as any).browser) {\n    const initialModels = context.Component.getInitialProps\n      ? await context.Component.getInitialProps(context.ctx)\n      await getInitialState(undefined, { isServer: true }) // get all model initialState\n      // : await getInitialState({ modelName: 'Home' }, { isServer: true }) // get Home initialState only\n      // : await getInitialState({ modelName: ['Home', 'Todo'] }, { isServer: true }) // get multi initialState\n      // : await getInitialState({ data }, { isServer: true }) // You can also pass some public data as asyncData params.\n    return { initialModels }\n  } else {\n    return { persistModel }\n  }\n}\n```\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003ePage: hooks/index.tsx\u003c/summary\u003e\n\u003cp\u003e\n\n```tsx\nimport { useStore, getState } from '../index'\nexport default () =\u003e {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    \u003cdiv\u003e\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      \u003cbutton\n        onClick={e =\u003e {\n          actions.increment(33)\n        }}\n      \u003e\n    \u003c/div\u003e\n  )\n}\n```\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eSingle Page Config: benchmark.tsx\u003c/summary\u003e\n\u003cp\u003e\n\n```tsx\n// ...\nBenchmark.getInitialProps = async () =\u003e {\n  return await getInitialState({ modelName: 'Todo' }, { isServer: true })\n}\n```\n\u003c/p\u003e\n\u003c/details\u003e\n\n[⇧ back to top](#table-of-contents)\n\n### Middleware\n\nWe always want to try catch all the actions, add common request params, connect Redux devtools and so on. We Provide the middleware pattern for developer to register their own Middleware to satisfy the specific requirement.\n\n```tsx\n// Under the hood\nconst tryCatch: Middleware\u003c{}\u003e = async (context, restMiddlewares) =\u003e {\n  const { next } = context\n  await next(restMiddlewares).catch((e: any) =\u003e console.log(e))\n}\n\n// ...\n\nlet actionMiddlewares = [\n  tryCatch,\n  getNewState,\n  setNewState,\n  stateUpdater,\n  communicator,\n  devToolsListener\n]\n\n// ...\n// How we execute an action\nconst consumerAction = (action: Action) =\u003e async (params: any) =\u003e {\n  const context: Context = {\n    modelName,\n    setState,\n    actionName: action.name,\n    next: () =\u003e {},\n    newState: null,\n    params,\n    consumerActions,\n    action\n  }\n  await applyMiddlewares(actionMiddlewares, context)\n}\n\n// ...\n\nexport { ... , actionMiddlewares}\n```\n\n⚙️ You can override the actionMiddlewares and insert your middleware to specific position\n\n[⇧ back to top](#table-of-contents)\n\n### Expand Context\n\n```typescript\nconst ExtCounter: ModelType\u003c\n  { name: string }, // State Type\n  { ext: undefined }, // ActionParamsType\n  { name: string } // ExtContextType\n\u003e = {\n  actions: {\n    // { state, action } =\u003e { state, action, [name] }\n    ext: (_, { name }) =\u003e {\n      return { name }\n    }\n  },\n  state: { name: '' }\n}\n\nconst { useStore } = Model(ExtCounter, { name: 'test' })\n// state.name = ''\nconst [state, actions] = useStore()\n// ...\nactions.ext()\n// state.name =\u003e 'test'\n```\n\n[⇧ back to top](#table-of-contents)\n\n## Other Concept required by Class Component\n\n### Provider\n\nThe global state standalone can not effect the react class components, we need to provide the state to react root component.\n\n```jsx\nimport { PureComponent } from 'react'\nimport { Provider } from 'react-model'\n\nclass App extends PureComponent {\n  render() {\n    return (\n      \u003cProvider\u003e\n        \u003cCounter /\u003e\n      \u003c/Provider\u003e\n    )\n  }\n}\n```\n\n[⇧ back to top](#table-of-contents)\n\n### connect\n\nWe can use the Provider state with connect.\n\n\u003cdetails\u003e\n\u003csummary\u003eJavascript decorator version\u003c/summary\u003e\n\u003cp\u003e\n\n```jsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\n\nconst mapProps = ({ light, counter }) =\u003e ({\n  lightStatus: light ? 'open' : 'close',\n  counter\n}) // You can map the props in connect.\n\n@connect(\n  'Home',\n  mapProps\n)\nexport default class JSCounter extends PureComponent {\n  render() {\n    const { state, actions } = this.props\n    return (\n      \u003c\u003e\n        \u003cdiv\u003estates - {JSON.stringify(state)}\u003c/div\u003e\n        \u003cbutton onClick={e =\u003e actions.increment(5)}\u003eincrement\u003c/button\u003e\n        \u003cbutton onClick={e =\u003e actions.openLight()}\u003eLight Switch\u003c/button\u003e\n      \u003c/\u003e\n    )\n  }\n}\n```\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eTypeScript Version\u003c/summary\u003e\n\u003cp\u003e\n\n```tsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\nimport { StateType, ActionType } from '../model/home'\n\nconst mapProps = ({ light, counter, response }: StateType) =\u003e ({\n  lightStatus: light ? 'open' : 'close',\n  counter,\n  response\n})\n\ntype RType = ReturnType\u003ctypeof mapProps\u003e\n\nclass TSCounter extends PureComponent\u003c\n  { state: RType } \u0026 { actions: ActionType }\n\u003e {\n  render() {\n    const { state, actions } = this.props\n    return (\n      \u003c\u003e\n        \u003cdiv\u003eTS Counter\u003c/div\u003e\n        \u003cdiv\u003estates - {JSON.stringify(state)}\u003c/div\u003e\n        \u003cbutton onClick={e =\u003e actions.increment(3)}\u003eincrement\u003c/button\u003e\n        \u003cbutton onClick={e =\u003e actions.openLight()}\u003eLight Switch\u003c/button\u003e\n        \u003cbutton onClick={e =\u003e actions.get()}\u003eGet Response\u003c/button\u003e\n        \u003cdiv\u003emessage: {JSON.stringify(state.response)}\u003c/div\u003e\n      \u003c/\u003e\n    )\n  }\n}\n\nexport default connect(\n  'Home',\n  mapProps\n)(TSCounter)\n```\n\u003c/p\u003e\n\u003c/details\u003e\n\n[⇧ back to top](#table-of-contents)\n\n## FAQ\n\n### Migrate from 4.0.x to 4.1.x\n\n1. replace Model with createStore\n\n`counter.ts`\n\n```ts\nimport { createStore } from 'react-model'\n// Remove typedef below\n// type CounterState = {\n//  count: number\n// }\n\n// type CounterActionParams = {\n//   increment: number\n// }\n\n// v4.0.x model\nconst Counter: ModelType\u003c\n  CounterState,\n  CounterActionParams\n\u003e = {\n  actions: {\n    increment: (params) =\u003e {\n      return (state) =\u003e {\n        state.count += params\n      }\n    }\n  },\n  state: { count: 0 }\n}\n\n// v4.1.x\nconst Counter = createStore(() =\u003e {\n  const [state, setState] = useModel({ count: 0 })\n  const actions = {\n    increment: (params) =\u003e {\n      setState((state) =\u003e {\n        state.count += params\n      })\n    }\n  }\n  return [state, actions] as const\n})\n\nexport default Counter\n```\n\n2. Remove Counter from model registry\n\n```ts\nconst models = {\n  // Counter\n  Shared\n}\n\nexport const { getInitialState, useStore, getState, actions, subscribe, unsubscribe } = Model(models)\n```\n\n3. update useStore calls in components\n\n```tsx\n// import { useStore } from 'models'\nimport Counter from 'models/counter'\n\nconst Component = () =\u003e {\n  // const [state, actions] = useStore('Counter')\n  const [state, actions] = Counter.useStore()\n}\n```\n\n### Migrate from 3.1.x to 4.x.x\n\n1. remove Model wrapper\n\n`sub-model.ts`\n```ts\n// 3.1.x\nexport default Model(model)\n// 4.x.x\nexport default model\n```\n\n`models.ts`\n```ts\nimport Sub from './sub-model'\nexport default Model({ Sub })\n```\n\n2. use selector to replace depActions\n\n`Shared.ts`\n```ts\ninterface State {\n  counter: number\n  enable: boolean\n}\n\ninterface ActionParams {\n  add: number\n  switch: undefined\n}\n\nconst model: ModelType\u003cState, ActionParams\u003e = {\n  state: {\n    counter: 1\n    enable: false\n  },\n  actions: {\n    add: (payload) =\u003e state =\u003e {\n      state.counter += payload\n    },\n    switch: () =\u003e state =\u003e {\n      state.enable = !state.enable\n    }\n  }\n}\n```\n\n```ts\nconst Component = () =\u003e {\n  // 3.1.x, Component rerender when add action is invoked\n  const [counter] = useStore('Shared', ['add'])\n  // 4.x.x, Component rerender when counter value diff\n  const [counter] = useStore('Shared', state =\u003e state.counter)\n}\n```\n\n### How can I disable the console debugger\n\n\n```typescript\nimport { middlewares } from 'react-model'\n// Find the index of middleware\n\n// Disable all actions' log\nmiddlewares.config.logger.enable = false\n// Disable logs from specific type of actions\nmiddlewares.config.logger.enable = ({ actionName }) =\u003e ['increment'].indexOf(actionName) !== -1\n```\n\n[⇧ back to top](#table-of-contents)\n\n### How can I add custom middleware\n\n```typescript\nimport { actionMiddlewares, middlewares, Model } from 'react-model'\nimport { sendLog } from 'utils/log'\nimport Home from '../model/home'\nimport Shared from '../model/shared'\n\n// custom middleware\nconst ErrorHandler: Middleware = async (context, restMiddlewares) =\u003e {\n  const { next } = context\n  await next(restMiddlewares).catch((e: Error) =\u003e sendLog(e))\n}\n\n// Find the index of middleware\nconst getNewStateMiddlewareIndex = actionMiddlewares.indexOf(\n  middlewares.getNewState\n)\n\n// Replace it\nactionMiddlewares.splice(getNewStateMiddlewareIndex, 0, ErrorHandler)\n\nconst stores = { Home, Shared }\n\nexport default Model(stores)\n```\n\n[⇧ back to top](#table-of-contents)\n\n#### How can I make persist models\n\n```typescript\nimport { actionMiddlewares, Model } from 'react-model'\nimport Example from 'models/example'\n\n// Example, not recommend to use on production directly without consideration\n// Write current State to localStorage after action finish\nconst persistMiddleware: Middleware = async (context, restMiddlewares) =\u003e {\n  localStorage.setItem('__REACT_MODEL__', JSON.stringify(context.Global.State))\n  await context.next(restMiddlewares)\n}\n\n// Use on all models\nactionMiddlewares.push(persistMiddleware)\nModel({ Example }, JSON.parse(localStorage.getItem('__REACT_MODEL__')))\n\n// Use on single model\nconst model = {\n  state: JSON.parse(localStorage.getItem('__REACT_MODEL__'))['you model name']\n  actions: { ... },\n  middlewares: [...actionMiddlewares, persistMiddleware]\n}\n\n\n```\n\n[⇧ back to top](#table-of-contents)\n\n### How can I deal with local state\n\nWhat should I do to make every Counter hold there own model? 🤔\n\n```tsx\nclass App extends Component {\n  render() {\n    return (\n      \u003cdiv className=\"App\"\u003e\n        \u003cCounter /\u003e\n        \u003cCounter /\u003e\n        \u003cCounter /\u003e\n      \u003c/div\u003e\n    )\n  }\n}\n```\n\n\u003cdetails\u003e\n\u003csummary\u003eCounter model\u003c/summary\u003e\n\u003cp\u003e\n\n```ts\ninterface State {\n  count: number\n}\n\ninterface ActionParams {\n  increment: number\n}\n\nconst model: ModelType\u003cState, ActionParams\u003e = {\n  state: {\n    count: 0\n  },\n  actions: {\n    increment: payload =\u003e {\n      // immer.module.js:972 Uncaught (in promise) Error: An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft\n      // Not allowed\n      // return state =\u003e (state.count += payload)\n      return state =\u003e {\n        state.count += payload\n      }\n    }\n  }\n}\n\n```\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eCounter.tsx\u003c/summary\u003e\n\u003cp\u003e\n\n```tsx\n\nconst Counter = () =\u003e {\n  const [{ useStore }] = useState(() =\u003e Model(model))\n  const [state, actions] = useStore()\n  return (\n    \u003cdiv\u003e\n      \u003cdiv\u003e{state.count}\u003c/div\u003e\n      \u003cbutton onClick={() =\u003e actions.increment(3)}\u003eIncrement\u003c/button\u003e\n    \u003c/div\u003e\n  )\n}\n\nexport default Counter\n```\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n[⇧ back to top](#table-of-contents)\n\n### How can I deal with huge dataset / circular dataset\n\n[Immer assumes your state to be a unidirectional tree. That is, no object should appear twice in the tree, there should be no circular references.](https://immerjs.github.io/immer/pitfalls#immer-only-supports-unidirectional-trees)\n\nImmer freezes everything recursively, for large data objects that won't be changed in the future this might be over-kill, in that case it can be more efficient to shallowly pre-freeze data using the freeze utility.\n\n```ts\nimport { freeze } from 'immer'\n\nexport const ExpensiveModel: ModelType\u003cExpensiveState, ExpensiveActionParams\u003e = {\n  state: {\n    moduleList: []\n  },\n  actions: {\n    setPreFreezedDataset: () =\u003e {\n      const optimizedDataset = freeze(hugeDataset)\n      return { moduleList: optimizedDataset }\n    }\n  }\n}\n```\n\n### actions throw error from immer.module.js\n\n```\nimmer.module.js:972 Uncaught (in promise) Error: An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft\n```\n\nHow to fix:\n\n```tsx\nactions: {\n  increment: payload =\u003e {\n    // Not allowed\n    // return state =\u003e (state.count += payload)\n    return state =\u003e {\n      state.count += payload\n    }\n  }\n}\n```\n\n[⇧ back to top](#table-of-contents)\n\n### How can I customize each model's middlewares?\n\nYou can customize each model's middlewares.\n\n```typescript\nimport { actionMiddlewares, Model } from 'react-model'\nconst delayMiddleware: Middleware = async (context, restMiddlewares) =\u003e {\n  await timeout(1000, {})\n  context.next(restMiddlewares)\n}\n\nconst nextCounterModel: ModelType\u003cCounterState, NextCounterActionParams\u003e = {\n  actions: {\n    add: num =\u003e {\n      return state =\u003e {\n        state.count += num\n      }\n    },\n    increment: async (num, { actions }) =\u003e {\n      actions.add(num)\n      await timeout(300, {})\n    }\n  },\n  // You can define the custom middlewares here\n  middlewares: [delayMiddleware, ...actionMiddlewares],\n  state: {\n    count: 0\n  }\n}\n\nexport default Model(nextCounterModel)\n```\n\n[⇧ back to top](#table-of-contents)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbyte-fe%2Freact-model","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbyte-fe%2Freact-model","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbyte-fe%2Freact-model/lists"}