{"_id":"react-model","_rev":"114-58488bb7badec2ddecb26df8ec8a7b31","name":"react-model","description":"The State management library for React","dist-tags":{"latest":"4.3.1","rc":"4.3.0-rc.0","alpha":"4.3.0-alpha.3"},"versions":{"0.0.0":{"name":"react-model","version":"0.0.0","description":"A model mixin for react components","main":"index.js","scripts":{"test":"make test"},"repository":{"type":"git","url":"git@github.com:jaredly/react-model"},"keywords":["react","model","mixin"],"author":{"name":"Jared Forsyth","email":"jared@jaredforsyth.com"},"license":"Apache v2","bugs":{"url":"https://github.com/jaredly/react-model/issues"},"_id":"react-model@0.0.0","dist":{"shasum":"7f5c3a0ae744834304910c537cbb9b176ba698c2","tarball":"https://registry.npmjs.org/react-model/-/react-model-0.0.0.tgz","integrity":"sha512-GpP8+h4gDsL3OFE149M1dv0AcYT90A8JK4fyRNUFpJ7m5WAur7wGEtamoGDwJZoW1AfS0QTw8Mg2EVnaPzFi1g==","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCKAy2tHS6z8F6uF4qipO9JqVawsfdgROK2SE0yVcvKZQIgdyTcWmvfFaS3icj2NG52letE/CdFRrBMW++QKU3e8pQ="}]},"_from":".","_npmVersion":"1.3.11","_npmUser":{"name":"jabapyth","email":"jared@jaredforsyth.com"},"maintainers":[{"name":"jabapyth","email":"jared@jaredforsyth.com"}],"directories":{}},"2.0.2":{"name":"react-model","version":"2.0.2","description":"The State management library for React","main":"./dist/react-modelx","module":"./dist/react-modelx","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","peerDependencies":{"immer":"^1.9.3","react":"^16.8.0-alpha.0","react-dom":"^16.8.0-alpha.0"},"devDependencies":{"@types/node":"^10.12.18","@types/react":"^16.7.18","@types/react-dom":"^16.0.11","immer":"^1.9.3","typescript":"^3.2.1"},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-modelx.git"},"bugs":{"url":"https://github.com/byte-fe/react-modelx/issues"},"homepage":"https://github.com/byte-fe/react-modelx#readme","gitHead":"6c6ce52f027b5243f34f18411a71c3f4d4724874","_id":"react-model@2.0.2","_npmVersion":"6.4.1","_nodeVersion":"10.11.0","_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"dist":{"integrity":"sha512-l6EdAeiKdnNv85bQneWnQeL5FA4/Cic7a7fQHkLzPjKkeU0oyZhbgngwC293uOdjOmV2EA3Mqr+XNjtjZea14w==","shasum":"cbbd8fe933c8794a694656c702bd5439bd86a78e","tarball":"https://registry.npmjs.org/react-model/-/react-model-2.0.2.tgz","fileCount":11,"unpackedSize":37815,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJcPZduCRA9TVsSAnZWagAANioP/11e8yEYcVZkSkjzOvQI\nH8/5T2QvixD/Z2opThIm20PnYLK1BM4FSyrFg9OGaTj/ictoL8Ve9rG7rCuD\nMgzjsMee4T42H/X5Y51ty/vYAVlnuGiXgmDLCBnZSKIFORECV5gYsMWN/GZR\nF/Yt6q2zD8ROs7wedUijtok/Qxp6rwrklyjwg4TuVDmdQ8XruInkBovdcn7f\nNAp+Vu7/oV3/lhEfmR8M3ojEy6Wqg/VyjLOXp8uVJboiEOEFgoCrZNzvCR2r\nipSsS5PQyExit1+TRkf1kN/1Fa7tiqfNRDKfkpZbYM+8JpjVtskOj5CBxr2q\nuSGaxqJKdQdLbOVb4SwH0HboAWw5mwKwZmfVtpM2u4lbU1VgqVrgEe3tstmj\npqQ1URhK1JRtpegus8UiUQaYW/AqQHScx90cpy6vWjBtRMPx99WgZV3MP1Pc\n61RKhpSwVBwSJ5RaVSgiMmnkSkmByxSONqSQoqYDndDr3YCQ0FpAGeYTQELx\nChtjs/JMqqFp19zRPZg3yIhBdoAwO3GJQD+MoKSOM/B7FqEx+pIFH2UI9tF7\n7K4+985fzhrR7VylAOv+KmGT9HpRrQeIopnj1l0H28Ab9B6XXF4OnoGLEUzv\nPKdL72cJXDfUNJSCZFyyEgN0knQdA10bQPqbyuiQIiE/55nGdjt5oHNodY1g\nIwmB\r\n=DmeC\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIEm8mS70lD4F4q0SQgmfyTUF1zhyQF3ioZDKfai5FGv9AiEAqk40AUiNEaX4t7so6iIV+oFjPr4oFW2Gwjt+fJoiDcw="}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_2.0.2_1547540334219_0.06408792555231568"},"_hasShrinkwrap":false},"2.0.3":{"name":"react-model","version":"2.0.3","description":"The State management library for React","main":"./dist/react-model","module":"./dist/react-model","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","peerDependencies":{"immer":"^1.9.3","react":"^16.8.0-alpha.0","react-dom":"^16.8.0-alpha.0"},"devDependencies":{"@types/node":"^10.12.18","@types/react":"^16.7.18","@types/react-dom":"^16.0.11","immer":"^1.9.3","typescript":"^3.2.1"},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","gitHead":"68e6c4bf03b2cee40bab06092a8ac08505ed5ef7","_id":"react-model@2.0.3","_npmVersion":"6.4.1","_nodeVersion":"10.11.0","_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"dist":{"integrity":"sha512-g7iCUsH/4sk5zWO4OKjl3o9Y3F6xOM8oxnh/jNUStqEdUDW5GIwrFn+DL+qUr+jZm7odNxfO7iLCsOUuPCMqbQ==","shasum":"e739c470e959645070186a4c3ee1074873f3d99b","tarball":"https://registry.npmjs.org/react-model/-/react-model-2.0.3.tgz","fileCount":11,"unpackedSize":37793,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJcPZlECRA9TVsSAnZWagAAHyQP+wfgYj7VmDsrDN30Sjzr\nmN+F3Ri6w/hBasUDrBkKdhcz4pACUVZtoAsGHz9pqFeo48R37fRWSTQ/WFi+\n+AJDRBIex5c3mWTIadLe9LgFPVAbW2E8k+78iLBjwfIARWoKbJbmrI4dkCzP\n2LTDP6FNWbfbSO7Sa31ypIlWBRQl+RLPiK5dCWtnsDsPEgZTsJwxcbpIHTgc\n/vC3tm3B9OYtamoMJCotcNSfBTxPHH9ScYEXSCu3Ea3fuqwsN251WGlNv220\nG8TK0LRYFfcCxmsHlyrsufUTKS56lxwd76q7W1MnTWtMDP0t8frgktOzuxv1\n39tGGcaJRjCsDSgnY1ok7enYXaVVsT7LUBtHG2/yd80sghhZu9W7Ro/9VEaa\nSzv2sgk3jewvAV+HXx7L2sUyU2TA9bYFbfk5yxxzovWifFc99y9AAFd16WHp\ns5q3EBswvsLnRlsiMu2Xd4NJbsLZ47MCpqhWx+/sSjvZVV65ShFBdL47lW+Y\ndkkg8ifd/WVOAVSRP7OG1P9eqlVSfPDXQjQ9mEfuohg7flUDaEzwVizvYEfP\n3h9MK2DoVsQyfGSyNsxdh75v/Kp6lYduU31yntz9tMkqKZuF45WRmoFzuPCN\nxvohxA3NaDrBPCiYfo/VcAKUo2QrgBj+FBn4VQQI1qkhVqrK54WdWHBYE6yo\n8oKp\r\n=U7O3\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQC0X+fyX26nrKePwCHkW44bYxHUpPKkW81TOX+z+pM/sQIhAPeL851BCppr7TNI+NqyEIdte5xN9NtzHjmNOyipOK9C"}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_2.0.3_1547540803479_0.7395827984266907"},"_hasShrinkwrap":false},"2.0.4":{"name":"react-model","version":"2.0.4","description":"The State management library for React","main":"./dist/react-model","module":"./dist/react-model","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^1.9.3"},"peerDependencies":{"react":"^16.8.0-alpha.0","react-dom":"^16.8.0-alpha.0"},"devDependencies":{"@types/node":"^10.12.18","@types/react":"^16.7.18","@types/react-dom":"^16.0.11","typescript":"^3.2.1"},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","gitHead":"68e6c4bf03b2cee40bab06092a8ac08505ed5ef7","_id":"react-model@2.0.4","_npmVersion":"6.4.1","_nodeVersion":"10.11.0","_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"dist":{"integrity":"sha512-h4SU1JyRzBNDeEa+ALYZIvIlttAvWQVrvmfSbx/IJPOaPS3UC23V/EUly4lFYs0K5k8KAqe9955HjeqUpeZIyg==","shasum":"7414a68f9c85f005482963e7f2113c2024e89ed9","tarball":"https://registry.npmjs.org/react-model/-/react-model-2.0.4.tgz","fileCount":11,"unpackedSize":37794,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJcPZxxCRA9TVsSAnZWagAAcfkQAIS/DXJHTb5/zs023d7z\ndI7DoHP795C9NMYRonmyG2sSyzlmTt/byEimUSBe8AvD7Yp7JDqyZusVuxx1\nLLotaGoNYdfrQpmJcOey+i6n9fm/jIWm4azlO07PQpIUTWW4GBxSS0h3iOj/\nRPP/XXD7JliUXWRbxWxiKmrzJklwGlL8opaR+R4wo7U6WPd2vc/EfOPg1yun\nDPBinneKqp56fm+Ma23rrFR680h/gkKw8Sw5hjM+6qd6+N1K0Jj/cXQ6OFH1\nJEX6kNdjmsBTt5UeX8OBEZTfTcEHbfchrZEMZaxLhNKbEX6K1IPaA3WE7h+a\ng3L38ENoew9b6RPp9zEPUUI4ryZnch0ttRTwIcGnck5ZgNnHvF5yzOKhotnt\nvNUx33xjpqfUWkqFkr3VMEbR/OIoX8dQpeItMD8bRoI+ehu9OYtfnrwGcbqf\nmKAZgQ1z+n/hLd9zeXsqcujuUfVZdzXXkHVDKgndGL7+qZMWHg3uijusAcfq\n1Q/myEGo1npmzNSSSc+WnKZ/ZkFtAXmMvgA5s0URwjhuuyZpzEbd4hfQoyXP\nE7/cJMIqlLpmdJqor/Q/qVf+KA8TBHjSMGRxzzHkSXwVhtnErkDlQYwr62nV\nx3N+9aHQF910GPn5qeljFg4x+MK7brC2WIiEdxU7oBkaoKzmomLe6JriWGvX\nGVY+\r\n=5y2N\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDviRGahx+o+DK1mDz5rcREslDsX9hGATMBnQRIWXVqowIgQeLGrY8GTFgoGeA/yu37rPeIYP3ks4D3bqFkxCJhDX4="}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_2.0.4_1547541617303_0.9231704466097392"},"_hasShrinkwrap":false},"2.0.5":{"name":"react-model","version":"2.0.5","description":"The State management library for React","main":"./dist/react-model","module":"./dist/react-model","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^1.9.3"},"peerDependencies":{"react":"^16.8.0-alpha.0","react-dom":"^16.8.0-alpha.0"},"devDependencies":{"@types/node":"^10.12.18","@types/react":"^16.7.18","@types/react-dom":"^16.0.11","typescript":"^3.2.1"},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","gitHead":"5acebedc55acb7ca85b8ced40f11f774949a39f6","_id":"react-model@2.0.5","_npmVersion":"6.4.1","_nodeVersion":"10.11.0","_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"dist":{"integrity":"sha512-BpQZnKWeUjhUNviBHdMYxG7qGOW8AqaRs9j7UJt5IXYwJGIZyEeWCx9wHtIoqvHPf1QQDtY1t/yiMEFMmDKngA==","shasum":"eba4aad8e17ea44f127fb8f4e258f1ca0699d883","tarball":"https://registry.npmjs.org/react-model/-/react-model-2.0.5.tgz","fileCount":11,"unpackedSize":37875,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJcPukdCRA9TVsSAnZWagAAKEgP/0c+rTDHOXkUwkFEN3uF\nelo2skcvUNC3GW1CfgvXLZfkDvwEmjpel+GTW83xjddkBwYUFpPZhua3nYzB\nyTgk3+cAXqdGvM/2DgAz3/S+QkLmKBpZ5DkEk3Y8ImxkeQwjTvM06lU7Aoyu\n82mD0FvM5MAlkL8TDfSAOs1iJXRJ9HbGgPY8rxKm5LSylu7AtKpk0wlkWW01\nL4NliJYQmUChbeaNDI9fL7rqxQX5mPFPr7JKxKN5RYMqiW/V0Ltsn5tFZxhV\nNOhwtobs1GBfdfCJeDZ2KevC+mesdCgjq2rp21RoUzaR+27MUo0cfmkT2UIv\nSAZFcp9kvSag7DUnO9U3321kbD6dba1E4ooFkgijzK1Ggw2HM+YHPeNly22E\nE6m7NUPVtnyxgDBe1kDOtUkII2Zq8+Dp8uOqWJg9dgZyefm8Zn9wIr54S0Tf\nJajknr/Z/Eu4sCWSfvFMqkTDA+SyVqI8EmYZoeUQIbPQMDp+P6Dp39EQLjGg\nQUHF/DHJBT4iP7ZQtFRjAzn0BCNbkKEVAxvUsr492pGEctTjqnm9/kKDNsWf\nyKSH1CgLbFN7K/madShouGwOWB1vWjqEAiHuFPhZ5zvN7J0gGBeuA6p2aypk\nbbv+HhIvZ8xtVxt2xjJ//hDN802Yew1HTdjxmBH8bwSnffmV9LmYmjgurKsr\nnuAs\r\n=y8OR\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIE7Ve7pPXDbiFZMUw6TGqfitN5DLlYZ1QwtiWqpyi+3aAiAE9QabHPYJAtktZg66ChYytW/OTbiyT1H8duqHt7wFDA=="}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_2.0.5_1547626781003_0.6154521999879121"},"_hasShrinkwrap":false},"2.0.6-unstable.1":{"name":"react-model","version":"2.0.6-unstable.1","description":"The State management library for React","main":"./dist/react-model","module":"./dist/react-model","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^1.9.3"},"peerDependencies":{"react":"^16.8.0-alpha.0","react-dom":"^16.8.0-alpha.0"},"devDependencies":{"@types/node":"^10.12.18","@types/react":"^16.7.18","@types/react-dom":"^16.0.11","typescript":"^3.2.1"},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","readme":"# react-model &middot; ![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) [![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) [![install size](https://packagephobia.now.sh/badge?p=react-model)](https://packagephobia.now.sh/result?p=react-model) ![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)\n\nThe State management library for React\n\n🎉 Support Hooks Api\n\n👬 Fully TypeScript Support\n\n📦 gzip bundle < 2KB with microbundle\n\n⚙️ Middlewares Pipline ( redux-devtools support ... )\n\n---\n\n## Table of Contents\n\n- [Quick Start](#quick-start)\n- [Core Concept](#core-concept)\n  - [Model Register](#model-register)\n  - [useStore](#usestore)\n  - [Model](#model)\n  - [getState](#getstate)\n- [Advance Concept](#advance-concept)\n  - [immutable Actions](#immutable-actions)\n  - [SSR with Next.js](#ssr-with-nextjs)\n  - [Middleware](#middleware)\n- [Other Concept required by Class Component ( Not First Class, ONLY SUPPORT ON CSR, Welcome to PR )](#other-concept-required-by-class-component--not-first-class-only-support-on-csr-welcome-to-pr-)\n  - [Provider](#provider)\n  - [connect](#connect)\n\n## Quick Start\n\n[Next.js + react-model work around](https://github.com/byte-fe/react-model-experiment)\n\ninstall package\n\n```\nnpm install react-model\n```\n\n## Core Concept\n\n### Model Register\n\nreact-model keep the state and actions in a global store. So you need to register them before using.\n\n`model/index.model.ts`\n\n```typescript\nimport { Model } from 'react-model'\nimport Home from '../model/home.model'\nimport Shared from '../model/shared.model'\n\nconst models = { Home, Shared }\n\nexport const { getInitialState, useStore, getState } = Model(models)\nexport type Models = typeof models\n```\n\n[⇧ back to top](#table-of-contents)\n\n### useStore\n\nThe functional component in React 16.7.0-alpha.2 can use Hooks to connect the global store.\n\n```tsx\nimport React from 'react'\nimport { useStore } from '../index.model'\n\n// CSR\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button onClick={e => actions.increment(33)}>home increment</button>\n      <button onClick={e => sharedActions.increment(20)}>\n        shared increment\n      </button>\n      <button onClick={e => actions.get()}>fake request</button>\n      <button onClick={e => actions.openLight()}>fake nested call</button>\n    </div>\n  )\n}\n```\n\n[⇧ back to top](#table-of-contents)\n\n### Model\n\nEvery model have their own state and actions.\n\n```typescript\nconst initialState = {\n  counter: 0,\n  light: false,\n  response: {} as {\n    code: number\n    message: string\n  }\n}\n\ntype StateType = typeof initialState\ntype 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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (state, _, params) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    },\n    openLight: async (state, actions) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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<typeof Model.actions>\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### getState\n\nKey Point: [State variable not updating in useEffect callback](https://github.com/facebook/react/issues/14066)\nTo solve it, we provide a way to get the current state of model: getState\n\n> Hint: The state returned should only be used as readonly\n\n```jsx\nimport { useStore, getState } from '../model/index.model'\n\nconst BasicHook = () => {\n  const [state, actions] = useStore('Counter')\n  useEffect(() => {\n    console.log('some mounted actions from BasicHooks')\n    return () =>\n      console.log(\n        `Basic Hooks unmounted, current Counter state: ${JSON.stringify(\n          getState('Counter')\n        )}`\n      )\n  }, [])\n  return (\n    <>\n      <div>state: {JSON.stringify(state)}</div>\n    </>\n  )\n}\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\nTypeScript Example\n\n```ts\n// StateType and ActionsParamType definition\n// ...\n\nconst Model: ModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (s, _, params) => {\n      // issue: https://github.com/Microsoft/TypeScript/issues/29196\n      // async function return produce need define type manually.\n      return (state: typeof s) => {\n        state.counter += params || 1\n      }\n    },\n    decrease: (s, _, params) => s => {\n      s.counter += params || 1\n    }\n  }\n}\n```\n\nJavaScript Example\n\n```js\nconst Model = {\n  actions: {\n    increment: async (s, _, params) => {\n      return state => {\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`shared.model.ts`\n\n```ts\nconst initialState = {\n  counter: 0\n}\n\nconst Model: ModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: (state, _, params) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    }\n  },\n  // Provide for SSR\n  asyncState: async context => {\n    await waitFor(4000)\n    return { counter: 500 }\n  },\n  state: initialState\n}\n```\n\n`_app.tsx`\n\n```tsx\nimport { models, getInitialState, Models } from '../model/index.model'\n\nlet persistModel: any\n\ninterface ModelsProps {\n  initialModels: Models\n  persistModel: Models\n}\n\nconst MyApp = (props: ModelsProps) => {\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    <Container>\n      <Component {...pageProps} />\n    </Container>\n  )\n}\n\nMyApp.getInitialProps = async (context: NextAppContext) => {\n  if (!(process as any).browser) {\n    const initialModels = await getInitialState(context)\n    return { initialModels }\n  } else {\n    return { persistModel }\n  }\n}\n```\n\n`hooks/index.tsx`\n\n```tsx\nimport { useStore, getState } from '../index.model'\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button\n        onClick={e => {\n          actions.increment(33)\n        }}\n      >\n    </div>\n  )\n}\n```\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 hook\nconst tryCatch: Middleware<{}> = (context, restMiddlewares) => {\n  const { next } = context\n  next(restMiddlewares).catch((e: any) => 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) => async (params: any) => {\n  const context: Context = {\n    modelName,\n    setState,\n    actionName: action.name,\n    next: () => {},\n    newState: null,\n    params,\n    consumerActions,\n    action\n  }\n  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## Other Concept required by Class Component ( Not First Class, ONLY SUPPORT ON CSR, Welcome to PR )\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      <Provider>\n        <Counter />\n      </Provider>\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\nJavascript decorator version\n\n```jsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\n\nconst mapProps = ({ light, counter }) => ({\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      <>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(5)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n      </>\n    )\n  }\n}\n```\n\nTypeScript Version\n\n```tsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\nimport { StateType, ActionType } from '../model/home.model'\n\nconst mapProps = ({ light, counter, response }: StateType) => ({\n  lightStatus: light ? 'open' : 'close',\n  counter,\n  response\n})\n\ntype RType = ReturnType<typeof mapProps>\n\nclass TSCounter extends PureComponent<\n  { state: RType } & { actions: ActionType }\n> {\n  render() {\n    const { state, actions } = this.props\n    return (\n      <>\n        <div>TS Counter</div>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(3)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n        <button onClick={e => actions.get()}>Get Response</button>\n        <div>message: {JSON.stringify(state.response)}</div>\n      </>\n    )\n  }\n}\n\nexport default connect(\n  'Home',\n  mapProps\n)(TSCounter)\n```\n\n[⇧ back to top](#table-of-contents)\n","readmeFilename":"README.md","gitHead":"1f552ea700b50c66586ed13d78e2bd900c7803ce","_id":"react-model@2.0.6-unstable.1","_npmVersion":"6.5.0","_nodeVersion":"10.11.0","_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"dist":{"integrity":"sha512-uvFKOpSQyRr0/q+r25Bdj8lvuu7NUclXXvtoN7iw5PyBaAOe0XIGBUGmdrJl3ejkiUexOsNg/UI0/7WID/xULg==","shasum":"bbcc7780f51990ce33a03978372f41e455aa4c95","tarball":"https://registry.npmjs.org/react-model/-/react-model-2.0.6-unstable.1.tgz","fileCount":11,"unpackedSize":39571,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJcQKM3CRA9TVsSAnZWagAA2ygP/0nJ8zAfXm9aYPWGfufD\ndy4tt94h8eTHmYNmx43He2+g/Tsfd0i0avfjssN5oViLOahkdvY5AhhvI9AY\n660U3KzX4OQeQFg0oLlLw9x6zwE8tA4REXqtrUn0bIby9zvJ4yz2eTvM4ES/\nCwZrhJv8MRpClRyCEOf9Q2h4f4eM+EbhjMtehRkcUVsc6jXsyk3H2qmCsPu5\nB/A0H2foMp1ziZimi5uRCkcTTymzc5geLnQh5KoS7JOebQbNF9b3kU0PaeqM\nB+hTq3XOUoNjizw8+f2psZflb7n20UgQ1miARbySSIeBYx/rEwtdslx+d1OW\nHgcz2ewoAkPn7xsICQeAHTG0dzL8tvNIaynV3l/YcLAfWcx+1JpbkEdLZylL\nLl+dA6mCndIfJdWHzdp6P2htIdVuSXWyFDQ4ImkNOfkx1CfaVpL5j9WqECr3\nYa0VFrHw4ZZQ7UVyNG+F9A6zDoo8AQmMGMnwvjGXDDpVIOIfqmNTlgTCrWNM\npk7i25E36ZPw+NNemyyVzzRrOh8xIHahm84efTorfxjpW4BvBtanjKGOpt7k\n64+U2wbIITPMqylBBEoJdbIz3H2Q/CNnbu+trnCdGFnnstsNIvV8DuiogL0d\n5tcQamFJq4gQCLnW620+XS7azHyOitC7llz5erSFrn3Mj3u0NPZWgQ0abkeD\nrbQt\r\n=lW3q\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDDM3lkyw+Js2r3yJsKNm9OcESxQWpxV5eEUKkbNwJ3wQIhAMieAU+V2YA0A8SRynOBGWHcpBCzEYYoEye9tFQWO6/S"}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_2.0.6-unstable.1_1547739958624_0.7941610838704012"},"_hasShrinkwrap":false},"2.0.6":{"name":"react-model","version":"2.0.6","description":"The State management library for React","main":"./dist/react-model","module":"./dist/react-model","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^1.9.3"},"peerDependencies":{"react":"^16.8.0-alpha.0","react-dom":"^16.8.0-alpha.0"},"devDependencies":{"@types/node":"^10.12.18","@types/react":"^16.7.18","@types/react-dom":"^16.0.11","typescript":"^3.2.1"},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","gitHead":"e8e28d45aca5dce86b70e70cc49f86360809607a","_id":"react-model@2.0.6","_npmVersion":"6.5.0","_nodeVersion":"10.11.0","_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"dist":{"integrity":"sha512-08x5KeDe+Ca3hH+dgaG765bTwbxiqPdqeXII0qQ4dZH9jx6P/rq0I7nfWU7B116NULlf8A5/ThGs7Du9Usgtfg==","shasum":"c52147b41688e6fd5e9a1e57efa9449df2797702","tarball":"https://registry.npmjs.org/react-model/-/react-model-2.0.6.tgz","fileCount":11,"unpackedSize":40307,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJcQLieCRA9TVsSAnZWagAAd/0QAJEy1E+h7gQC9QonfRh8\nqYAa9hkhf8FsyLz6ue1sDT/nnDeQhpaOLYrt5cImveVhaRDjPvljb90Zi/Wy\nY4trpg7vgCP1AUqDtUdvV/YJO0T5im3ks2Hnso8QPNYDDtstshrReSvaWuCG\ncit2WD514+7rpnH+CKSvlnSnXwjdEsOergvs1fuX15/Cuo9TT+2L/FTqFQqG\ngKzwc9q4gbbn53TUiwh/sadGphAVMaz1ZysBDlS/0Nvew+d27WqymurIWvDK\nz68iM9Pw+HPTNa+qHsdW4G3J2h1ELRIV9MK9JMoU2OKkredDPZYycg7izDL1\nsvY1TfQaiuaE7Gwbozf2glvzQtdopRz9P70D7HKws0uJ0vDP95F7bpTCA90D\nT3ljK0/VzZhOy4r/5OLRi8yVrJpqClH89SVWOz3/CRSw/qb/0CarRSTsRx8J\nCC+eAWoKB5WCYnJXIyryYMyjvzL8J+GczJKZHOO5Wj695zUERT5v9iHudRi8\nSt41kZzFobegx65sGcxzXU77dLgTRYgVyNI5KXfWD4SkfdIIRK5cZxIrUsLn\nTWnlXAbtNMoG3EZL2GtoXXc+//NFRy0cpF8hFOMjhF+UK0GmJyigJi1KKM/y\n2x4LLlYBJfy7T8HkpMhpo/WOUWzOXnuTHUIX4Q6RVb49Cm6Tdj9GkCGEX9Iw\nc7hp\r\n=LiAm\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIDscBeosL+xd9TCQiRp4NST250ySaYSGH0tAh4TXeEqiAiEAzeX7YoV769JLYA5xTMgdC0GXb95Kje579vjHQqd6dA0="}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_2.0.6_1547745437587_0.16192667201023148"},"_hasShrinkwrap":false},"2.0.7":{"name":"react-model","version":"2.0.7","description":"The State management library for React","main":"./dist/react-model","module":"./dist/react-model","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^1.9.3"},"peerDependencies":{"react":"^16.8.0-alpha.0","react-dom":"^16.8.0-alpha.0"},"devDependencies":{"@types/node":"^10.12.18","@types/react":"^16.7.18","@types/react-dom":"^16.0.11","typescript":"^3.2.1"},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","gitHead":"e8e28d45aca5dce86b70e70cc49f86360809607a","_id":"react-model@2.0.7","_npmVersion":"6.5.0","_nodeVersion":"10.11.0","_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"dist":{"integrity":"sha512-93bdIDooca4sTltnryJQUYKk22d11KHIp5XD+L8J+1juQtwqT3m+p7Ll73VjRJRClJ1lYQIaC+29ag/TnAysUw==","shasum":"376bcfa19755ede1317396dbb789ea2f50b1c09d","tarball":"https://registry.npmjs.org/react-model/-/react-model-2.0.7.tgz","fileCount":11,"unpackedSize":40748,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJcQUsECRA9TVsSAnZWagAAOZMP/R8hlCWn657XjTVGpTF0\nE6Up1ojk5ePulgTDFano0KoW+7tKXzS4eArTbblJyxsfswkv5Pu/emCRsPBM\nPQqNm5LrOtjG2U7roGIAe3zm1dvCDCSjmTxUBGgHT+l7t4SXRmueqHeDYBTp\nChK0px6iiITQFl2bi49HwBXfn+mkCltitpywsryIfdME9F7+pCzjujyiXu5N\nc+2aad6y+GJdirSC7cy51sWGyMc1kzVBVDmfExPl7CqKuoHztruF+JaTTDv+\npF+GSSKtOUNLNNRZJQeu6AE2C03RdecyAwovTO6uy0tC2OY3JOVGAEhr/LaH\n+jtRd4/iH4CXtgYafshCq8vaRfU9h2gpp8B7rAao8YDK6jd62c3ttpG4JKGs\nj2DJqkkZw5FLRVoLFpBWpThHC8UpALTvvFUoc5TBIK5yDvXpzVlx40eBz6Yo\nSnkL/fLCGT/1CqPbmclPA1KrMs9sjfSFd+UOWn6lTwNqL7etF4uASqVFeAay\n+RSO7YZWGgC9cxgMZt2t/kB5U4dh4KbEArpxVVCmf/4MUCZb4e6PPfziVBgi\nqZ536eyN0q8vc9fK7vzEO2kCRGiEC3FS++ecDrpHGgwSeRz7r73zXsfOihfa\nOP/wGTnacFvr5mejQoa0m7zIsz5t1FLiZ4UIZjF7WaJ9Lmwc9Pap52mnqF87\nwKkH\r\n=FGwK\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCICLni3FiXT2bavWHGbuB7cjQSPVoytiuKe+zWkyK1L80AiB5XMIdNxhVsHHafDg1OL12sPRtJBhX7APJjUGkyH+JDQ=="}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_2.0.7_1547782915605_0.44289908845905646"},"_hasShrinkwrap":false},"2.0.8":{"name":"react-model","version":"2.0.8","description":"The State management library for React","main":"./dist/react-model","module":"./dist/react-model","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^1.9.3"},"peerDependencies":{"react":"^16.8.0-alpha.0","react-dom":"^16.8.0-alpha.0"},"devDependencies":{"@types/node":"^10.12.18","@types/react":"^16.7.18","@types/react-dom":"^16.0.11","typescript":"^3.2.1"},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","gitHead":"dc103e496508baf9dc758ee456c093399727cd2d","_id":"react-model@2.0.8","_npmVersion":"6.5.0","_nodeVersion":"10.11.0","_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"dist":{"integrity":"sha512-lstAL5T63F33Y6h0OPlPR6k6gvIKqqgqLROjnTmaB65i+j7MjcTic59Irbpx6rZy/Kp09QuNHiGF6D64AQo9Xg==","shasum":"83d590cf51fa2b248a45007e2a9ca700be127297","tarball":"https://registry.npmjs.org/react-model/-/react-model-2.0.8.tgz","fileCount":11,"unpackedSize":41329,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJcQU35CRA9TVsSAnZWagAAIC4P/iS5G73dbZnwdR+1QL16\nYSga2myRRjjKtb/sQPsDKrjGm/U9NdI0whl52KoXzXzHFVJyuMeUKfQB3rsW\n2agSIC89j3h4aO5gbyqGGUwlWc1/pfjbCCjXurtqoLRpCvqqVKLSzbwO+r1O\nBLHMmYyMMcLKSsmb/qqGZMIaYJCUxScvb48RSbj5IDwtuM9fBjW4a6Dc5k7R\nMmryXHOX2b2WjaVBTkNi1uy+jb2abmmlnFhb+0kgFjMgIjv0GaQCKQ0sbkAO\nRjbCHbW9s/G8pw5aegkDDRztvg6qLRrGG/g7Iirk8Cy4YhcRvSb5/hAuFXYt\nlwwBAUnrCJGldJIehivleiFAnP4rg705MAkEzOUEDjyHsTap6QK17H033Zm8\nXqeiyQjogojHrMnFEYDB52yIas50cEL1IhNOIy91dn/ztFopBF+eaeHXatXK\nXFsyCSF59oNQffY4FRwtud+VThQqo9xjipz3WCVXWUDuei1LPVucmjBrgR8G\nSI9ajLasC+UoW2hwT+kcPIj8pQV8jIWwMZ6wX4E4HifRzf6NereSJoEVUgRg\nYVMvwxmVloUoJNtKM/wXDDWt8F667XvsZHttWFLGb964ptZCQgqE7bGAUYFM\njW9unFBw6dmyxxNvHQ6Zf8xuyENbXknwzMUCu8o/8AR7v9ddXGLSw/rQz3wZ\nSiMf\r\n=zG5l\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIAwNMSNrbuX2DAdHXBl/iozUUcPDIFUvb7ZCITRDRWI3AiAeykksLxGmvu/tOBmc1Lw7Wg0AEPXk2oh+X7L6PCegNA=="}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_2.0.8_1547783672591_0.5837866818983306"},"_hasShrinkwrap":false},"2.0.9":{"name":"react-model","version":"2.0.9","description":"The State management library for React","main":"./dist/react-model.js","module":"./dist/react-model.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^1.9.3"},"peerDependencies":{"react":"^16.8.0-alpha.0","react-dom":"^16.8.0-alpha.0"},"devDependencies":{"@types/node":"^10.12.18","@types/react":"^16.7.18","@types/react-dom":"^16.0.11","typescript":"^3.2.1"},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","gitHead":"ea7da61a55c4045993f3989d8ea5a54724c3d401","_id":"react-model@2.0.9","_npmVersion":"6.5.0","_nodeVersion":"10.11.0","_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"dist":{"integrity":"sha512-tkJtkfA2aEQyAxMPAXJMijDNfqoI0ftclE3KvGNOMp4zNK2uMpiYj9mHgCd9QSNStbmldQVtPOyiV4fAQX0S2g==","shasum":"e6890e1e2d787278d312a87171f5673388acc654","tarball":"https://registry.npmjs.org/react-model/-/react-model-2.0.9.tgz","fileCount":11,"unpackedSize":41416,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJcRHizCRA9TVsSAnZWagAAhVAQAJE2KVFeAASkiXZCxJu3\nNohN4I5IZ40fAezfmsamynBM3Cx+WHNHykXsFxiyWByHAGPJFj7a01cohmQ9\nhrY6aiNkxm5W79ywdvh7aWVfNWcfvDfqbOdcu73rBAkYvtMVxj6arqwZ2+Tq\nR5YdrcMwVERsbx/E8/cmRwRciWym4ASXjlsuQSSkrC8NJzBFBLmy6ulcmeHV\noH7UKvv4Aw/Kt0nIjZ5j+zfwE5w3zjaiggjsVtx8PWvW3xY/4uZzBUcPQwVk\ngnFIQt4XErfwMcUlTovzUr9aBeZiKO/rKyJOtjgWLx+7EAL5Ml/3Y2694qQ8\ntz9bvHQP0tAFx1aQaN+hl3Cah+7vn1vJ+nW2mqSIHpG3vcHI2sF0A0b88OjB\nsaI30kyMvcxzslfkdH/aAfvdtYH9Iy/e9/vlPLH/CVXDPzi1Rl/K5UTapocY\n8gM9fXK/KvewvAr1KWtC06mw2ZhhBnLIG14swSSsQrV9N028ZuIOzmZ4JdbL\n6GDUZsEdaYCzBeblV46Fpr9DRpAZOQ1SuXdbOnNDfTpUBnWlGhRGtruAeIP7\n+IY9tdFSfkthw3CMQTIH7PoM1mfEqyf0XH+EQGd1ratr+z/VfyAX+O/l/N9l\nsqlvjOOwGfIr+u33dxiD1I4+aRUcba2SL/n26KsyRLgDiE8/rZqrO/NQse5G\nKSC5\r\n=DtkV\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDFbUzg0ufkXoY1DeBpL5sHh0FpqGPgvVxsWbyaUsGE2wIgXhdwcOtydKxf1YfdNMm+xPZRh20PWjECN5W1IeWe998="}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_2.0.9_1547991218859_0.11668933633069956"},"_hasShrinkwrap":false},"2.0.10":{"name":"react-model","version":"2.0.10","description":"The State management library for React","main":"./dist/react-model","module":"./dist/react-model","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^1.9.3"},"peerDependencies":{"react":"^16.8.0-alpha.0","react-dom":"^16.8.0-alpha.0"},"devDependencies":{"@types/node":"^10.12.18","@types/react":"^16.7.18","@types/react-dom":"^16.0.11","typescript":"^3.2.1"},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","gitHead":"e9710403d98948df10b2f58480776181968a7f33","_id":"react-model@2.0.10","_npmVersion":"6.5.0","_nodeVersion":"10.11.0","_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"dist":{"integrity":"sha512-Le/2fKaIVwr/Vge/biyIsNBgxuAQnvao4n7+ytjI5sq5cTpDMIplNiCuPge3ZfM4KMPS/VcBpcyQB/udUw5F9w==","shasum":"ff5c97ef0fe855ed496dfa18f6ea464028da8bec","tarball":"https://registry.npmjs.org/react-model/-/react-model-2.0.10.tgz","fileCount":11,"unpackedSize":41497,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJcRIE1CRA9TVsSAnZWagAA7G8QAJW+ZDwZcGJYGhaimWZl\nVvJug75V/DL8bgaNAj9e+7siF16L4/ZH/gedj0KO3OpZ6z9WPwG/hNZDg0IW\nOEpQrdOyi0JSLkS0nxkoeKSJKGrzr0fy0t8I4IUq6ndXwyI6Qcjfz9ZSmslL\nK5ou7/qDXof76ZbCsFOosnwZcW7zTTI9SEp9GJPsLhPTwAHDtOSw0Nk4kR7r\n0DHV98COuFFLJ+4/DVOAQrhXFJabJZEK0l8bYOd4zLGdoE7cTmiYDPEkTYF5\nP5Yk8M6L2iJe10pybdoa4LSRq4DKKtql/jWJvosgIaNcn1dwhdqpjESCooNH\n6zeMIqhW9vDB+wl+luHEYV9Xutx9rrJR6hsh6QOF8g7AumrVuf8ahleuROFP\nEw8tdw5vqoSbz5zhR0b9cugMx4/5PQ+F8E5ZMubEKF2M8M617a0k/iHlPqRM\nGGuYU1HBTBKvIRzhAuPoVJ5ziHfHWTOX/r7h3BSxj2n5HTD2HrxgCgiAtj4d\nKakeIrSW46UXAC7ab8U/jpScjFQmRhJCyi4q4zgJVXAwSb7urrztg11q2ULg\nvM/3yORbk3gZfFyQMRvkyTfKmJ5Dz2QDvayZGNkUxG7WcbyfH5YQiHUNzUFK\nAZPibCwH3p6AE5hy1gdCwGfeuLj6Yef6bok8V8zm8kOj4ekOGETpcTutkWTD\nV48W\r\n=8IBV\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIG1quH/KB3GlgeHQA/6BpBRH7/xGkXu3mIpLtNFW/E5uAiBLxrnzadWpCOUVthngJm7RAnCf0kdUXzNECGhjb2nrMQ=="}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_2.0.10_1547993397243_0.41466138033686706"},"_hasShrinkwrap":false},"2.0.11":{"name":"react-model","version":"2.0.11","description":"The State management library for React","main":"./dist/react-model.js","module":"./dist/react-model","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^1.9.3"},"peerDependencies":{"react":"^16.8.0-alpha.0","react-dom":"^16.8.0-alpha.0"},"devDependencies":{"@types/node":"^10.12.18","@types/react":"^16.7.18","@types/react-dom":"^16.0.11","typescript":"^3.2.1"},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","gitHead":"e9710403d98948df10b2f58480776181968a7f33","_id":"react-model@2.0.11","_npmVersion":"6.5.0","_nodeVersion":"10.11.0","_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"dist":{"integrity":"sha512-4Peq+s4l6ETGxBwoRqsYHJZdiHy/IhMmuRbAreN0h5X6xe91iOLOB012W9RCs4dvdXjW2RlTpIpPQJz2Pvm88A==","shasum":"64f09a705cf0b3e8d38a0e78aa5599b9b28fd993","tarball":"https://registry.npmjs.org/react-model/-/react-model-2.0.11.tgz","fileCount":11,"unpackedSize":41544,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJcRJ4jCRA9TVsSAnZWagAA5uUP/iAZHRBPe5NFy6n8EpzL\nNyaCYqcWpo6gh+PYxifAd0HX/THqJot/Cbtd2pEwmIKYia7Ag9v0QxdIRd7j\naP/6xd3yOGuHkEjmrQgMtLdqMhzyYPqnAIUec15dySCtkuvPBhcKnsVLpKqj\nXzpJgBe1tpMP+676VUcHd73l+4dqDPNmnm2W4gefHyJHDlzYJV933rutJm9D\nvx9nMAXp//1Y4aneuN+TX/+ajZi5IK0XttsvRsC7krWgjwPg73AdfgOsFlJm\naYvY3dsngHPoZS3MCiByL3FMm/6JE30XZ3qCqBQNGhyYPpw6OzhxMYZWSnYa\nwSBH+rXm0X9kt97iKSeM+T9iuBJjWyg6/IsEEnCp7DUa47JEzKEFoMrOBzC1\naNdkBPBEv7PWqLw8vl7ZSZYUbdR3dsImzKGpZxU2ZM1RbGGVbiqqrkez9Kts\nRrP/UkxcnLamy1yb+G6f/HQr7MItiOsEDU840kF8RoPfKEEwAVmpFR9t6j06\nSsf4v0JUCOxQGcXkK8YtseDGPbYb688+gHCmOw3y13Deiz2ToZKm+XBZb8Hr\nBJyuDdt3/gFmwIVK7/KYcyuRrBHAnAFW3FYYOV1HqohJ+n2k9zU7bHHOsV7h\nz889A3M4cHkIvOkhfdhd3xhZ7QrDGI+NCpg6hxxreFRT0mp/x86NPOJUHNrd\nMcWB\r\n=iRF5\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQCPp65TsakM+ZELMXRavC+nHSvD6VXkEzITlO2kGqEcSgIhAKrwvRDorA2Zab8FdikID5Cy8CV1rxA/pIESZtGh42aL"}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_2.0.11_1548000803085_0.20542149649256958"},"_hasShrinkwrap":false},"2.0.12":{"name":"react-model","version":"2.0.12","description":"The State management library for React","main":"./dist/react-model.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^1.9.3"},"peerDependencies":{"react":"^16.8.0-alpha.0","react-dom":"^16.8.0-alpha.0"},"devDependencies":{"@types/node":"^10.12.18","@types/react":"^16.7.18","@types/react-dom":"^16.0.11","typescript":"^3.2.1"},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","gitHead":"e9710403d98948df10b2f58480776181968a7f33","_id":"react-model@2.0.12","_npmVersion":"6.4.1","_nodeVersion":"10.15.0","_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"dist":{"integrity":"sha512-X884dUWNx8gTASoHGE2rnQCsC/BSXqN1aGRnaj5MsWr7Y8BBmkvB/wmPa1nVk14QGo3hhMdXA1kqvEVwJEOxSg==","shasum":"99b5ee9e57c3b8895a14abaed3ce84711a3ddd91","tarball":"https://registry.npmjs.org/react-model/-/react-model-2.0.12.tgz","fileCount":11,"unpackedSize":41488,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJcRWPfCRA9TVsSAnZWagAAamAP/A6GJZR3K2982I4eSc4C\njW7mOqGPOdrbd3vZOVuDOcITRqSeOVlcx0QgOlGnonx+jYnzxrjvJX7NvCEC\nYVgGuPev08XZ0/gudcMLlWPb/JqAg4kPaBOIsYs1CJ8vb/ZOm/n2/X4KJ5kZ\nmc1eiyTUXg/I56QdUxcY60CEsucZotlBhGw79Rvd0tgm9OX0wqKkVDdbfKeC\nc55RNb4dtJJbu++XFT8ITsNQEiCTxnBLjGVyXMn74NZxX40fEDP9XO2gSf+W\nOw4Sdb6bI/0hzmSCgSDdwmk717OalYsPhvlNZkx4UCSuL8zHz/OGy8PTsFxW\nU7JGtAVgy5R7KEcmnGp51BKArtsKerV52+wxvrehU4lc/CllGkIacP63HzW+\nPS32fyx3bdyGQkoPjGfQG51GAcw3uBzyyZ/9Yk3nwWbjVdcEVMAouVxwJi1L\nkjKq44lrEV7AvnEL+kui2RpU+EkdRfBhSUCsRZkboNwNV3nRvaaYC9MqJ3Yz\nADgam8WG+LuGwao8dfbdywCh5PbJWLKeS6M5xvWHP+XkqsK0aRFY5PPpHLAC\n4d4WQ3cdOIYY14dQ1kCU6Jd3rhIjApLLDs4I0inIW5QfVZckxqvNe6dwIlLT\na44WTSf7LJvQNpKxcfnlgiA5XDKL1qkR+nnJDfUaHthVig7rPBvqWhy55gMi\njJnR\r\n=75/8\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCICzp0lOAwdTphCFFjkJrpGr4SQ51setLcMAeklxfp1fcAiEAps1JEbdtmuzA2c1BT3Czqjz4zrELdl3Ipt/zVLzOXSU="}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_2.0.12_1548051422964_0.9433142450140759"},"_hasShrinkwrap":false},"2.0.13":{"name":"react-model","version":"2.0.13","description":"The State management library for React","main":"./dist/react-model.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^1.9.3"},"peerDependencies":{"react":"^16.8.0-alpha.0","react-dom":"^16.8.0-alpha.0"},"devDependencies":{"@types/node":"^10.12.18","@types/react":"^16.7.18","@types/react-dom":"^16.0.11","typescript":"^3.2.1"},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","gitHead":"779bb9e87e671496671497a42991248e3e95f286","_id":"react-model@2.0.13","_npmVersion":"6.4.1","_nodeVersion":"10.15.0","_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"dist":{"integrity":"sha512-70BoipXWECu43o96iCH17oCQ6hfiyo/2c/F7ctOPCafOOC+HIulaFfwcwvuf7DRxFalYYyv2oAq0H3FsggEQLg==","shasum":"72c4c24769d62faaf9ef13011488764207786509","tarball":"https://registry.npmjs.org/react-model/-/react-model-2.0.13.tgz","fileCount":11,"unpackedSize":41740,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJcRpBBCRA9TVsSAnZWagAAD2oP+wR2JdsVagYC6c75cG6s\nXoTLXJwI+cwk+cZQRkj7a98PNvFkMUSp35plCC+sWlTR6RjUaf7SrD3FO+dp\nbP+v2zjOyNlpe+e74EovQpjnWMTbTfoDXrnVFicmxdYV4FGcHYos4l9MrWJk\nUNG6lq1x7IUgyodvauUrxn3Y90uSg5NauwWqBaMTs62arbzmxGevxSGs9Ffg\nByOaaRPGtw+io66+58jBGlenZnCxevrysHXpcI3zRgdVG7zTaTzBtJyPszpI\nKHmsZ+N65mqXrirZk02jhCV8CPxEka51Vbgpcj6jUihSp//nDhUHl1j50vuJ\nDpa1sKdJr6gLqXBMPT6ad5qXqzGblijAZaS6lW50evbGaFyhLHemTT8Vqqdj\nMceu/z76dh61HqIw68A7b1hqjwmYjc9aFne31N4sZ/BT1z+x1UWrjudnwq0x\n3SB6Qrfwt37S02lgC2xTaWKlx7Je+1ElY2dugkgrsgA6rmEMa5mWrEe+628T\nqqZCZkRGjD8isay/1tylA1/Xyk33ezdhUBXYtgu/8OGF7skLA+g7gmb6mDV7\nDbtEj4J5vsAoM4p3lnW5VjhQXs0Xm9kd0W8J7XGAvD+H7rBSG9j52MDGBG+7\nU/mpak56oAO8MgSbvJLfVF0ylL8EGPT4JgsfVTxxlL38ejQPALSw/vx2JSCg\nBg4m\r\n=17Ce\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIGrkvLfsJUFo7eK+BFPROIq2WCxsf9k2zqsPctJ/BE37AiAuMjGZtckwyPTwsf88EQAQ5qFbekzBcfbQaErnzmRHEg=="}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_2.0.13_1548128321125_0.8639345635925191"},"_hasShrinkwrap":false},"2.1.0":{"name":"react-model","version":"2.1.0","description":"The State management library for React","main":"./dist/react-model.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^1.12.1"},"peerDependencies":{"react":"^16.8.0","react-dom":"^16.8.0"},"devDependencies":{"@types/node":"^10.12.21","@types/react":"^16.8.2","@types/react-dom":"^16.8.0","typescript":"^3.3.1"},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","gitHead":"bd16695e972a7be27a60ce19fb1b98d6e6591e2b","_id":"react-model@2.1.0","_npmVersion":"6.4.1","_nodeVersion":"10.15.0","_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"dist":{"integrity":"sha512-/8DLkzajCpsIWbIUpbu8BtEKF69v3c+uf0wc7Ht/yLsfy13mGxkqiALedxnEEtlqIYiX4VvsBdq3y7v0nLxkXw==","shasum":"61ae29ce1f37fa417cfa937ad5e3d76b208dd3d1","tarball":"https://registry.npmjs.org/react-model/-/react-model-2.1.0.tgz","fileCount":11,"unpackedSize":42283,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJcWvQSCRA9TVsSAnZWagAAv9cP/icxXHlNWjA1nqKEtye1\nla0aq9ccXwgtMMq2h47AgiKn3+VkSbmFxkhuNBZX3U2oEUJcOJgJbsEDzudT\nYVlTKeWHutsrigpZGtH9uGlqxT45AAQtSk831yvUrrvxI4IwnoN7lcfo+aid\nIT93eQpuGWXzzvolJeBUq/IwC3dRNAS4sZuZEk2tWY5dUahS6sVy27NIj2ry\nPXQWuZ3YIvsuIAso5RMyHBoMrsxHLLf/Cn2yTc1Iv1ylUpICSuBNdyggp0l+\no8tMGyYznKqUQYE2JyV7h04cGTcGM0HDI8IRHMYsfwtXq/j2wwMDjB4yXIgM\n4d+yDLHg+B8q2h9lscWRX6pD36ydbrO5zvpYJlN9hVpGBLwIHXVPlf3X0bNG\nqKkn2vzkF8hyPXEBiaJPSthxy/wPlHG/SHAYh/L88eifR5uQF2nLvVKch9sG\nykw94YxtAI5shKVXOu92qCLZzkXWrwQy2BiXqWZ4zjuFS93P9MbNDY9ohMuP\nA50DHMuXgnP778t/IPqLmGGsLF8M4tkPw/Z7UeOAgPkMN/Q/DkLGK3i78nmK\ndewYP6wpxu+7I2+W1SCGwel8uNAZiqeVa+1X+7wJLf3v/kmwl9N8oTIUNxvp\nIOrJSoKtmn1keotko6qj054A0UmABf8EsM/tpshEcFHYNl8LYMHfFpQwDeX/\nJmTj\r\n=kXUX\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQC9fJnuKLldN19A8iAvvFMVN5TxdDUZOvMFdnI/CAb91AIgaOUsOP6I/ymQeXRUMoKOkLAh+0VPIEuG29YpeiKb6UI="}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_2.1.0_1549464593600_0.3308174948626068"},"_hasShrinkwrap":false},"2.2.0":{"name":"react-model","version":"2.2.0","description":"The State management library for React","main":"./dist/react-model.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^1.12.1"},"peerDependencies":{"react":"^16.8.0","react-dom":"^16.8.0"},"devDependencies":{"@types/node":"^10.12.21","@types/react":"^16.8.2","@types/react-dom":"^16.8.0","typescript":"^3.3.1"},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","gitHead":"afb432bd5d791465dccdb7c286bd90173d960137","_id":"react-model@2.2.0","_nodeVersion":"10.15.0","_npmVersion":"6.8.0","dist":{"integrity":"sha512-rIsjr99zc4UvL7ex9CWUoLMpRMcMjyrrO5HE8tpIvLJzjioUcYmN5HMDkGhz7QZQCfV/Nfw0iGiJrJU+25SY+g==","shasum":"cd7f085a00f39f7243d6670b82266015db2fea18","tarball":"https://registry.npmjs.org/react-model/-/react-model-2.2.0.tgz","fileCount":11,"unpackedSize":42595,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJcaQpsCRA9TVsSAnZWagAATc8P/3LuatYmpA3MFtuFe6+D\noCk3Dx2Jiqr+rMr112N6XfVvIP4YqZdVhBJAjJHyZaHQ/o0rnqB55/KVYEbj\nv60P+VVOlHrMhBiLbF86O197ZgplKo7oLTgAGOnLC9M0C8z90lNM2Kogt+JL\nf/zc3jddtdpuUDs242Q7Usn491Fy2TZPRFFZYKQkM6rpyhY7I4m2Qq1kSZJG\nJHoWankZDKuasY0P0NHzk3TKDajxD6gDEItU7BJC3X0pOLCnBsm8toYFd4/Y\n83sSy5B5NAoCkKOApbg9GvuLD0HgxBPxZ534+XDBVSpN/xz52AwSM8g80R/I\ngiwToc05ZYISdpySWzX68mmDBVNFZX1+mGzFP0IiUKnG40bntqeo5lOMMcRA\nD1ox+hUW6baK18e858VHVthkLEaPEc8lx0lLit5MoXZaIP9KEgAqtDO9HWCZ\nwrhpvlo6TLsujyBOgvr0hLTjHHP4Rcjjazvyl5GBVR6Ti6ql3QOl5wLpOiFc\njIcZOTsxY/cYOJyClmbyIVP2WE5fnE87rw/tpyHvSNUVU6sTcpcZM+r9r/Yj\nIbWq9jcNdiqOMJmwEWX95S06mIIXvwe7QlHUdCw+HQ/iMw7N4d/SslZ8Q8sM\natXMQnYrqCi2ArB7Hx1MPAxKG0D/zTqF+WBKmEuAf2BHWs+Acd5T4S91MAv2\noA+T\r\n=jAme\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIHWbEeavQnsXgPjmVvl80f3CW4xXH9naxI8CmlJ/i57MAiANenXkch0SE6zSXJQWLQrpUvXfxK+zwchrVyV9SE0ppQ=="}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_2.2.0_1550387819853_0.8798072408106565"},"_hasShrinkwrap":false},"2.2.1":{"name":"react-model","version":"2.2.1","description":"The State management library for React","main":"./dist/react-model.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^1.12.1","react":"^16.8.2","react-dom":"^16.8.2"},"peerDependencies":{"react":"^16.8.0","react-dom":"^16.8.0"},"devDependencies":{"@types/jest":"^24.0.5","@types/node":"^10.12.21","@types/react":"^16.8.2","@types/react-dom":"^16.8.0","dotenv":"^6.2.0","jest":"^24.1.0","react-testing-library":"^5.8.0","ts-jest":"^23.10.5","typescript":"^3.3.1"},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","gitHead":"d86acea22c8f308768054445b68e3b5071f3b4a2","_id":"react-model@2.2.1","_nodeVersion":"10.15.0","_npmVersion":"6.8.0","dist":{"integrity":"sha512-9TNt1ihltqK3V7mb5A64Zd5VwIdfCHnsEzoh7e7EFHbi6H2FLESkkHl+B0I5d3BrzgnYmm8e7Q/38bdYUStL0w==","shasum":"4357774fe61f1c918dba701a34bf26e55e09c478","tarball":"https://registry.npmjs.org/react-model/-/react-model-2.2.1.tgz","fileCount":12,"unpackedSize":45452,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJca5gDCRA9TVsSAnZWagAA680P/1w1g8f+Y8cGSKRf5/yw\nmmGE5w6i9uzZhaZEfLyVMZefov8WjLM3mTx0bxDBPjuvahZ6NgSG2MGLAbmV\nFg49GzdfLC28tjBcIeX2AQxLPDkyssnUNKC7RDVNavAHKe1Np6d3Rq4c+Xti\nZJbTPTag24uOy+n8S8TDc7LPriHbBCtTqO2e3LngR1qyg5aU6pYWE2NujkAR\nX88oijxxRKX9XXPiDDQuK0cub+srkZ8Urlili5sn0L3tHKm8f2f7AaNknuH8\nciZrPUZ+ocqfFK/toxM3p61qFXvY8KGzedhVNbf0yRUOMbhzuoURj04hV/eg\nWZteQB7tinzctqvu+8P9zoaQb0dHHabzgRi7pOyxi3wxZQLy2t1GDindMrEs\nyQAR+H8XW7hGJGaR7OZLMxVO1Pdfipw4piG6pPZK/KMdzRb8sA5GuzYaol2u\ndQVZZVwVVx7vXkqoir2TRmDQXQvctA+SZjhWyTAXJVTZxuR7mbn9NDloiEbX\nfRZUiTvVbjP5XAqlMF25L493++HTXXOl/I3PoVrJGlXskpBV0u5FiF/dKTUQ\n7A66+2P+296LokTLBervbhWGo3KsoxWX7M+PF6mWYIw490rR16/EkKUG/q0/\n0BOkBNUKENXPs0sAGtEkGVu0A8AahH7E9YFr33ra5PkdlOb43CQQJjYk39BU\n5HiA\r\n=mRwV\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQC566hUbt0aM4yxHjrFHwa/MqXGUG6wv3FU5mlhxYxSJwIgbS5FXBDb737tAH6ERt/Ys+CUkTvq8COmfe+x1Efm1Ik="}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_2.2.1_1550555138154_0.9793138063252189"},"_hasShrinkwrap":false},"2.2.2":{"name":"react-model","version":"2.2.2","description":"The State management library for React","main":"./dist/react-model.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false","commit":"npx git-cz","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^2.0.0"},"peerDependencies":{"react":"^16.3.0","react-dom":"^16.3.0"},"devDependencies":{"@types/jest":"^24.0.5","@types/node":"^11.9.4","@types/react":"^16.8.2","@types/react-dom":"^16.8.0","dotenv":"^6.2.0","jest":"^24.1.0","react-testing-library":"^5.8.0","ts-jest":"^24.0.0","typescript":"^3.3.1"},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","gitHead":"27643e6bbd858f5f9d1ee00bacee121544987af1","_id":"react-model@2.2.2","_npmVersion":"6.4.1","_nodeVersion":"10.15.0","_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"dist":{"integrity":"sha512-jMl+H1yI0qUPlfKBKczJ+2b5Mq4bamKI6AyV7KSYXdMW29kGzIEE1TnyOLaVqr1YF5Pwc5tgGPsEANfShccVVg==","shasum":"63f02ebd389b5f608de52d8e76ac5b4209d0d26e","tarball":"https://registry.npmjs.org/react-model/-/react-model-2.2.2.tgz","fileCount":12,"unpackedSize":46323,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJcb9cfCRA9TVsSAnZWagAACEcP/jXS8ZfRI49U9k67SGJi\neasA3Y0JCAZuUh4ofrUqWj5k4xbtDLezGcDl8yqpAG0XaYOWlJV1X15UcXbr\n8lk4x/AlIZi4ukFSJXck8thV0Ijgk6KzqWnxrAAqKyvhiAtuckmZQyshoDhW\npLN2mcoGNNAWlormCa7wZYDjn2Pka+n7bnnGg20s7bpZRMeOfXc/Bei83Iii\nTRVw0F/Rsom2xMd90Ms1Bxf78X8FwQLOP0oUR+ORIt3UoCXJd3dYGsjcKBq2\n6yg6GSNuFk6JsNHef1TVEAIJNjDDSGbhxhFskIPmYyJP+GG3qPV9sqnmkYlT\nkqt6VVVg8hyRZxuJOjDSzNCOA+5oNSDtHla57i7FknvpNNfqPPkKTqWcPQY7\nU9nVxcJAaYGu8u6vI3XG1uVgkHtlyotoq/4+i4GksX6G9RJU06rbil5sWwWr\npPkgKHmM/fJJi/Z6P2+dBwTJzBlgjfLVd24djNCacMjJGgxpw6iQXYE91rlw\nqUQg7WIODaiQ2yPR+8pvwCHYyoEl5DrllNvmTrUnVJLY51TtELvJAPYYWBlx\nNSh/Bg+81xeueEFV3iDeV0+MNqb9wrxB7ITF1PLY+yfmFk+70ZyaOrJO+1cD\njGnlrawkTkMWz4XiswIOF53BYxMJcUjHQ58FEAjLbZ3k36HRmWsc3k8ws/Cm\nsOBh\r\n=FePv\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIHmMI1DoFfg5W0pEecSTH7e5sgUStMH+1eFTL66RR/+EAiB/e1Ka6nPf8av/dh9PXOXMbdEBAIYT+CJxTce+AL0xxg=="}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_2.2.2_1550833438080_0.9213761908332119"},"_hasShrinkwrap":false},"2.3.0":{"name":"react-model","version":"2.3.0","description":"The State management library for React","main":"./dist/react-model.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false","commit":"npx git-cz","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^2.0.0"},"peerDependencies":{"react":"^16.3.0","react-dom":"^16.3.0"},"devDependencies":{"@types/jest":"^24.0.5","@types/node":"^11.9.4","@types/react":"^16.8.2","@types/react-dom":"^16.8.0","dotenv":"^6.2.0","jest":"^24.1.0","react-testing-library":"^5.8.0","ts-jest":"^24.0.0","typescript":"^3.3.1"},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","gitHead":"85753f105dc4f638192062f5ea8ee5d4cc89932d","_id":"react-model@2.3.0","_npmVersion":"6.4.1","_nodeVersion":"10.15.0","_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"dist":{"integrity":"sha512-yoKK5AHsB1CTy0x4VLnM9evz3sfj6nl6cN2SOwOIHNfMFOH/vBn6otANK1BAeBbgZOmB+HFEJBZsRiTUp7dC3Q==","shasum":"f69c811b55d6764e87f396592296a37420b70a64","tarball":"https://registry.npmjs.org/react-model/-/react-model-2.3.0.tgz","fileCount":12,"unpackedSize":45967,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJcc7M2CRA9TVsSAnZWagAAzZsP/2I1/vy2quUNktDRivU9\nXepFBhBNT0UoT+10Nhcx+c4cDMoztRnAPxV0GGuJJ+XQ+NxBecekUhLq006Y\nXKeCZgk0xTo0d4YJuHxfJfSSDe1MBTkDbQ5GWIeyUUPYlndhTj5al7SieA/0\nYjiPktgD2cBToairOVbzH/07DART/jFl1uL1e1XatlayVpUOTep+EGa6e57E\n6lRzwfYtOqj8siphNG0vzYeFecxDmW6qKz72Y7KkuKq+J56H4DHp3hMh3BfT\nEEh0L0cUqj48zBFbfWPpAWT6pHL0XwTn01DuVo7Qoe28R1clNJwWiEfjS7Bt\n09W7bg/YR6fXX/iDx0XtF4S7qKSix8UkrwOVLbkhASNqO6bE03ipylK3IUws\nKX8wULTCVnAHzfQx6QwHi7vixv16I+utgCi0wYZuVBWuiI5nXjQ15o3kenYE\naQGg1NQqBDTuab7Zn4XHwTr8a9BKcTWhfw7i+ztl3MEIwmHMELm2xElZWl59\naOf8fk9BCinFG0QXfjFkNiKIxAVbIgD4Gp27PsyT6KS9itKAx3qZsng9ufwk\nTJHbirBc2m3oG2Er17vptzgDf8GKXD7MfUJczqQqKLHEbM6NzugYTRWJ6N76\nTQlML2YVIhHP/lUoGjoFS3vxKJV9cvAp2zyZNRmwUiUJ/Rrxr0+7ZULBrEPQ\nq4eh\r\n=2UcC\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIE8VgyVf26G4OeiZPcLwUBpBhAnCspOlT3Vyw08LvuQWAiEAkM+nB0S58GKhwib3S9jAsvERAGi1p/GZ9JLyOOqqgSs="}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_2.3.0_1551086388291_0.6064181557874371"},"_hasShrinkwrap":false},"2.3.1":{"name":"react-model","version":"2.3.1","description":"The State management library for React","main":"./dist/react-model.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false","commit":"npx git-cz","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^2.0.0"},"peerDependencies":{"react":"^16.3.0","react-dom":"^16.3.0"},"devDependencies":{"@types/jest":"^24.0.5","@types/node":"^11.9.4","@types/react":"^16.8.2","@types/react-dom":"^16.8.0","dotenv":"^6.2.0","jest":"^24.1.0","react-testing-library":"^5.8.0","ts-jest":"^24.0.0","typescript":"^3.3.1"},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","gitHead":"16f3605d2dc805d494f29911749535955403b9ce","_id":"react-model@2.3.1","_npmVersion":"6.4.1","_nodeVersion":"10.15.0","_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"dist":{"integrity":"sha512-tDliJycc8V96FzmoGDVl2HZwInxdAlLmfsjW3Rw3aZ0BGlXH5Rj2lhHuF36kCcD9ZjEbF/Z98VRNamsNLODfiw==","shasum":"2dc824841183d5762ffd66fe4dd6481b807efd3a","tarball":"https://registry.npmjs.org/react-model/-/react-model-2.3.1.tgz","fileCount":12,"unpackedSize":46729,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJcdNDJCRA9TVsSAnZWagAAMgwP/jrySDYAxkITZGH07pTL\nRklJ/lao68gnH2tMJI67RxxzhCeCmzQ7pc+b7TNmOWt/9andYS0ZEs9xLtP1\n0uDGrQ2L1o7vAkomNTdCQ6cttf7FaVzhsBp0FEduGYxerAHXeDbSfqyYDAp0\nPNJBNUvQBWH1rLHasL5LCMDpcU6jtRDQoa6Wi8bfrZaa4lcbhzm0H9cGYSfa\nuaouSE37SvdHlYwq3jKDgpQQsW4mOzMtkoA7FmUf+KIL8Pd+tDB8Rimy7Pdk\nAoKFcfThbrXaMYAkuSCNqm/ufkNmo9rh+5RC9Tm2xfKVn0gScSTm9QsQSqWn\nsDBqkOAzCIVo2iOFLrPdEw2R7Qh1Bfny2az8+iP5LMgjF/OMsdCKKyxSnt5e\ncwm7gIuUEB0q5S0q1W7iMER5cniMdClF44jzdHTnzWS1kwQOpirFGlEZGUVH\nuZpmL9OthBVd2CShGU97XZrj0pMdepqT6tNOxFyn6BTY4OZnR/YX32hdx/TF\nrkDTvAhJB5zk5HT26FYB+qT2XTXU5mecVDIJJeCyF49PHBl2tBFKnQdmVPkz\nBnz1oea+carhXZ2P02n7PKmcPj2zm1KfxN3HsTzbLQnzWvgDQbdPElrOL+/b\nH3pSzhYcO8eGhr4MImAkJScOCti0LAPrls0lN1SW4NqMXsZc7dAo7+BxMFl/\nhjJi\r\n=MtKp\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCICDxX0W5v9KSNwnqLoW78uWWyqisGYrU8E5RAE2NxbgAAiEA19zw/nrQDUhN+Hx2I4hms02T0UDdHKF177l526Ae8bU="}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_2.3.1_1551159497051_0.2117627136165976"},"_hasShrinkwrap":false},"2.3.2":{"name":"react-model","version":"2.3.2","description":"The State management library for React","main":"./dist/react-model.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false","commit":"npx git-cz","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^2.0.0"},"peerDependencies":{"react":"^16.3.0","react-dom":"^16.3.0"},"devDependencies":{"@types/jest":"^24.0.5","@types/node":"^11.9.4","@types/react":"^16.8.2","@types/react-dom":"^16.8.0","dotenv":"^6.2.0","jest":"^24.1.0","react-testing-library":"^5.8.0","ts-jest":"^24.0.0","typescript":"^3.3.1"},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","gitHead":"2cb6c0e8bd0bc58b160f70b60b8ac308c858657f","_id":"react-model@2.3.2","_npmVersion":"6.4.1","_nodeVersion":"10.15.0","_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"dist":{"integrity":"sha512-AzXFNyvkNLyacp9DGTFiBBB2pzj6xC7PCQukyn/9JX8lUWvTvqhd0zWzPlraZbemoVcpyarQHocB9QrubqZtBA==","shasum":"6490f08950e716847c6ceecfa8674b68e7bd4d47","tarball":"https://registry.npmjs.org/react-model/-/react-model-2.3.2.tgz","fileCount":12,"unpackedSize":46860,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJceNYxCRA9TVsSAnZWagAA/lQP/2arE7O6nLUSGdlv10iY\nDxYtyAuMKMG1gx6gN3txFcGYhn3uQN8gGddmQW8EYVyPGFWh7v9UzLl4/tka\nctzkFUttKJ4j4hfUX2HiA1FoVoViwdPASO3H6nVBqYd/Facl6muTMAsfMBq4\nQo8+sbxU+unGV4TJzL8+lK6biOu3qAIfMK4j7iH2jsGdeymi1wC6yL+dW/Ua\nHDTjXO/c6stIc1C2JB5uoO/nVNX5MWLfvcKZ5DhcKmeccUMgdVmiDlziKk82\nWv208GT19Tj5OqWbX+mrsty3EB+1VxbiygIthsIC3xeaWoSWpwv7ej4SA5tx\nXVspHCP7C9sTtVNLVIg4UOhAdNbBT7q2q6oMhm/Y1n6UbQZYBP6/yxfX5Ms7\nn+EQFFE3eGJPG+xS+q6gm5XRRoZ0fQ9AUkJnYcwTxpHXDNxntEP94InHPkIE\nKhaVJoD4Ze3COPOGV2zgkHqHSK7cED2sXG6dUUbZv8IMS5UmPiK7JsXd7Zth\nxWILHmjtP26UfdyBokQj/fHjE/FUXb1GiIyfltuiBxmXCtmEJLz6gai6pxbP\nHaOA6dVCxRsjKBmj9IzLe/V7IamaYc7E/XHMVNsyHAYzbh6sJG0Z2ZhGL814\n95YS80pws8yNhFfW2dW9ndbrh5kCLPBFtS14L9dCFdJKIfJSjHMRmCZBGNIL\nYudQ\r\n=mbv+\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCQ6icX2uA/EbUGd5Owq5reE+yraxrlKNGFeW4dI4H4rQIgUdzluEhU1xSppHVGU8aoPyiiVt+h1XROhnZuD3l+0SY="}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_2.3.2_1551423024522_0.4280130120896055"},"_hasShrinkwrap":false},"3.0.0-alpha.0":{"name":"react-model","version":"3.0.0-alpha.0","description":"The State management library for React","main":"./dist/react-model.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false","commit":"npx git-cz","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^2.0.0"},"peerDependencies":{"react":"^16.3.0","react-dom":"^16.3.0"},"devDependencies":{"@types/jest":"^24.0.5","@types/node":"^11.9.4","@types/react":"^16.8.2","@types/react-dom":"^16.8.0","dotenv":"^6.2.0","jest":"^24.1.0","react-testing-library":"^5.8.0","ts-jest":"^24.0.0","typescript":"^3.3.1"},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","readme":"# react-model &middot; ![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) [![Build Status](https://travis-ci.org/byte-fe/react-model.svg?branch=master)](https://travis-ci.org/byte-fe/react-model) [![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 Hooks Api\n\n👬 Fully TypeScript Support\n\n📦 gzip bundle < 2KB with microbundle\n\n⚙️ Middlewares Pipline ( redux-devtools support ... )\n\n---\n\n## Quick Start\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\ninstall package\n\n```\nnpm install react-model\n```\n\n## Table of Contents\n\n- [Core Concept](#core-concept)\n  - [Model Register](#model-register)\n  - [useStore](#usestore)\n  - [Model](#model)\n  - [getState](#getstate)\n- [Advance Concept](#advance-concept)\n  - [immutable Actions](#immutable-actions)\n  - [SSR with Next.js](#ssr-with-nextjs)\n  - [Middleware](#middleware)\n- [Other Concept required by Class Component ( Not First Class, ONLY SUPPORT ON CSR, Welcome to PR )](#other-concept-required-by-class-component--not-first-class-only-support-on-csr-welcome-to-pr-)\n  - [Provider](#provider)\n  - [connect](#connect)\n\n## Core Concept\n\n### Model Register\n\nreact-model keep the state and actions in a global store. So you need to register them before using.\n\n`model/index.model.ts`\n\n```typescript\nimport { Model } from 'react-model'\nimport Home from '../model/home.model'\nimport Shared from '../model/shared.model'\n\nconst models = { Home, Shared }\n\nexport const { getInitialState, useStore, getState } = Model(models)\nexport type Models = typeof 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 return from useStore can invoke the dom changes.\n\n```tsx\nimport React from 'react'\nimport { useStore } from '../index.model'\n\n// CSR\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button onClick={e => actions.increment(33)}>home increment</button>\n      <button onClick={e => sharedActions.increment(20)}>\n        shared increment\n      </button>\n      <button onClick={e => actions.get()}>fake request</button>\n      <button onClick={e => actions.openLight()}>fake nested call</button>\n    </div>\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 with depActions and React.memo to prevent child components rerender frequently.](https://github.com/ArrayZoneYour/react-model-todomvc/blob/master/src/components/TodoItem.tsx)\n\n[Demo Repo](https://github.com/ArrayZoneYour/react-model-todomvc)\n\n[⇧ back to top](#table-of-contents)\n\n### Model\n\nEvery model have their own state and actions.\n\n```typescript\nconst initialState = {\n  counter: 0,\n  light: false,\n  response: {} as {\n    code: number\n    message: string\n  }\n}\n\ntype StateType = typeof initialState\ntype 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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (state, _, params) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    },\n    openLight: async (state, actions) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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<typeof Model.actions>\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### getState\n\nKey Point: [State variable not updating in useEffect callback](https://github.com/facebook/react/issues/14066)\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> Hint: The state returned should only be used as readonly\n\n```jsx\nimport { useStore, getState } from '../model/index.model'\n\nconst BasicHook = () => {\n  const [state, actions] = useStore('Counter')\n  useEffect(() => {\n    console.log('some mounted actions from BasicHooks')\n    return () =>\n      console.log(\n        `Basic Hooks unmounted, current Counter state: ${JSON.stringify(\n          getState('Counter')\n        )}`\n      )\n  }, [])\n  return (\n    <>\n      <div>state: {JSON.stringify(state)}</div>\n    </>\n  )\n}\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\nTypeScript Example\n\n```ts\n// StateType and ActionsParamType definition\n// ...\n\nconst Model: ModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (s, _, params) => {\n      // issue: https://github.com/Microsoft/TypeScript/issues/29196\n      // async function return produce need define type manually.\n      return (state: typeof s) => {\n        state.counter += params || 1\n      }\n    },\n    decrease: (s, _, params) => s => {\n      s.counter += params || 1\n    }\n  }\n}\n```\n\nJavaScript Example\n\n```js\nconst Model = {\n  actions: {\n    increment: async (s, _, params) => {\n      return state => {\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`shared.model.ts`\n\n```ts\nconst initialState = {\n  counter: 0\n}\n\nconst Model: ModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: (state, _, params) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    }\n  },\n  // Provide for SSR\n  asyncState: async context => {\n    await waitFor(4000)\n    return { counter: 500 }\n  },\n  state: initialState\n}\n```\n\n`_app.tsx`\n\n```tsx\nimport { models, getInitialState, Models } from '../model/index.model'\n\nlet persistModel: any\n\ninterface ModelsProps {\n  initialModels: Models\n  persistModel: Models\n}\n\nconst MyApp = (props: ModelsProps) => {\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    <Container>\n      <Component {...pageProps} />\n    </Container>\n  )\n}\n\nMyApp.getInitialProps = async (context: NextAppContext) => {\n  if (!(process as any).browser) {\n    const initialModels = context.Component.getInitialProps\n      ? await context.Component.getInitialProps(context.ctx)\n      await getInitialState() // get all model initialState\n      // : await getInitialState({ modelName: 'Home' }) // get Home initialState only\n      // : await getInitialState({ modelName: ['Home', 'Todo'] }) // get multi initialState\n      // : await getInitialState({ data }) // You can also pass some public data as asyncData params.\n    return { initialModels }\n  } else {\n    return { persistModel }\n  }\n}\n```\n\n`hooks/index.tsx`\n\n```tsx\nimport { useStore, getState } from '../index.model'\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button\n        onClick={e => {\n          actions.increment(33)\n        }}\n      >\n    </div>\n  )\n}\n```\n\n`benchmark.tsx`\n\n```tsx\n// ...\nBenchmark.getInitialProps = async () => {\n  return await getInitialState({ modelName: 'Todo' })\n}\n```\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<{}> = async (context, restMiddlewares) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: any) => 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) => async (params: any) => {\n  const context: Context = {\n    modelName,\n    setState,\n    actionName: action.name,\n    next: () => {},\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## Other Concept required by Class Component ( Not First Class, ONLY SUPPORT ON CSR, Welcome to PR )\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      <Provider>\n        <Counter />\n      </Provider>\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\nJavascript decorator version\n\n```jsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\n\nconst mapProps = ({ light, counter }) => ({\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      <>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(5)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n      </>\n    )\n  }\n}\n```\n\nTypeScript Version\n\n```tsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\nimport { StateType, ActionType } from '../model/home.model'\n\nconst mapProps = ({ light, counter, response }: StateType) => ({\n  lightStatus: light ? 'open' : 'close',\n  counter,\n  response\n})\n\ntype RType = ReturnType<typeof mapProps>\n\nclass TSCounter extends PureComponent<\n  { state: RType } & { actions: ActionType }\n> {\n  render() {\n    const { state, actions } = this.props\n    return (\n      <>\n        <div>TS Counter</div>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(3)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n        <button onClick={e => actions.get()}>Get Response</button>\n        <div>message: {JSON.stringify(state.response)}</div>\n      </>\n    )\n  }\n}\n\nexport default connect(\n  'Home',\n  mapProps\n)(TSCounter)\n```\n\n[⇧ back to top](#table-of-contents)\n","readmeFilename":"README.md","gitHead":"a8703f4aaf8b1cf11c2c657bd46439bc6f58f9bd","_id":"react-model@3.0.0-alpha.0","_npmVersion":"6.4.1","_nodeVersion":"10.15.0","_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"dist":{"integrity":"sha512-R5PimUaGFSutJfMnTxdVsosWpb9ylxdMa87Pxg8tvhcFE87OkEmUgv5hLOaa0fvJ32/bsieZRAcQycZ4f/z73Q==","shasum":"703388737b1137ffb1f3b635b3895f43a7f6db4c","tarball":"https://registry.npmjs.org/react-model/-/react-model-3.0.0-alpha.0.tgz","fileCount":12,"unpackedSize":50475,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJcfQO6CRA9TVsSAnZWagAA9TMP/jZ5ydwgZ6yeXTRmmhi7\nHJRaYK5S55H/3R8l3rLTASSndgax3+neGWf7N3/dq/BWaIVL4K/zQ4ZdJnEQ\nhtNHP0kVHytVqAKsEt2KfmqkyzpDiwZ8svIvrMlDzmugYQ01zdYSVGTT4/Ki\nz5cKNg5VziKMLgvXn2yVz2nhLpLyvj4jufBT3geDo59NtUweTPn7306CF7I0\nqK6xyX60xZJpSk9j72NFFclILPkBKXeKCOymi4RsyE7xfXv7U8MEFUfuxIEQ\nikcFyqCatA79tRRKB4Sf8cEAM3o4Lv80hItU969FdxKMboyTNecR2EtU6F+y\npiOnckeRLlqLhcBjQkaG2ouwz1K4GXlM2u8uXpMjZKBpLI5fNkg6NzT95xcJ\nvBjeiNqpzQFOvOBF2SlG3ND/ZcEbxsp6uWfM0LqtwuIl2dsxcQu5vDZhokat\n4kovtnMWokyKS8UEOpYHUEJW5xHIXzdF87D1dQ5yDJCOLoSH0bjQc3jIm5f4\n6h+p0DmobeLemx5cF+A1Lj77P1i/12EQo3JGorqQ7pGaUy5v4jef8ak8+CrU\nQbSD1bocazRUHeORnpnv50FxBrGIaNmCuLN4XK1CEzxSAy91G7AYrx/x6OhB\nHdGy61mrtXGCRK8YMH9HJyH1saTZEO2Eg2AxeIrGQDV2uz2dc5TQHSAj8In0\ncZXn\r\n=Da9Z\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIAlClM8BxGzBAVcSpqwddij/uc6gxFqgQw7mVXNlAkcvAiEAuN/jAajmhDC30++laDRTSSmqB4TENgkHOD4O4k5St6w="}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_3.0.0-alpha.0_1551696826063_0.5892204787414934"},"_hasShrinkwrap":false},"2.4.0":{"name":"react-model","version":"2.4.0","description":"The State management library for React","main":"./dist/react-model.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false","commit":"npx git-cz","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^2.0.0"},"peerDependencies":{"react":"^16.3.0","react-dom":"^16.3.0"},"devDependencies":{"@types/jest":"^24.0.5","@types/node":"^11.9.4","@types/react":"^16.8.2","@types/react-dom":"^16.8.0","dotenv":"^6.2.0","jest":"^24.1.0","react-testing-library":"^5.8.0","ts-jest":"^24.0.0","typescript":"^3.3.1"},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","gitHead":"a9420462d2a94e35a572937ccebfbd2fa8ee50e4","_id":"react-model@2.4.0","_npmVersion":"6.4.1","_nodeVersion":"10.15.0","_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"dist":{"integrity":"sha512-69xvj3UxzBTWqwrUA5nSTVPp2X1LPQzFYTtGrGaMcMZW0dhI7mpoon3GNedA+hM9Dlu9809pPifAUI9VjWXwgQ==","shasum":"85faf608e71d60389eb07b2473435dea0384bb13","tarball":"https://registry.npmjs.org/react-model/-/react-model-2.4.0.tgz","fileCount":12,"unpackedSize":51193,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJcgP7tCRA9TVsSAnZWagAAzBAP/0lptB1jjl69rivXSd4T\n7O2ZBouQW93BgKEZKRCtCEt5TKWhYwgcDHz8hgATrPCbTbpn6eopyq9X+Y3W\nrb3KlnD1VrcT0amswtofpWzA+7ZMPodvK878kJNcrozxTdl4k3f9OV5dtA13\nuEjfd308DpsrO/AiLtQdqNnv6Bcmoqj8uiQXPROfqPx6j7UCPTGM0a65chF7\n2h/O2KastezK/aocxdZw+0eSANxLn6xNb7L07Ij877G1wg/CErJvJx8D66Xw\nRhXWHbygFO+nq7VVuf0LTVr+sZU3QmwhjNuyMMn3avbOtkyumbbzRIxUnz1x\no61xnPqm30U34ai6X70aDOkWogziXJ616FGullXxlsEUsfJZoVPDsmZWHqb+\n2mQVn0A8CgxKhTPkRl5FLXnphl4Nm2Z9at2z2DbJONcxw7oaRE3XmYQ8A4kO\nqG+gUhkMRGEHFM5IygdRO55vZfy6kmMJrslq4skVj2Tjp6VFX7ku3hFk2YqR\nAE7O2GyjS1ae1Kc+is0Tj3VlvNGwbZJn6Ykiaj0UwtaxKJ+WbnwZGG223Sn1\n3NoCWQx04gJ7MAZwMIrUkujAXxIRezJOtKVayzrcvJQCxfcGdxbz+1eXfYhw\nEhw+JpUKDNSZvEJAbuiJZFJcve42oWgUq9g4qSRztp27BH27dmsuC4P2bNbl\n8/Aj\r\n=zjfU\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQC9VCWBjogvb5fJ+ik2uNchK/H5N8F86Z89e5JZUSwWVwIgQBJ8k4KRsf/iqWt9UmNiUpEdPvQTEVhL7QbnDpNy8sI="}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_2.4.0_1551957739946_0.32869766474105555"},"_hasShrinkwrap":false},"2.4.1":{"name":"react-model","version":"2.4.1","description":"The State management library for React","main":"./dist/react-model.js","module":"./dist/react-model.esm.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false","commit":"git-cz","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^2.0.0"},"peerDependencies":{"react":"^16.3.0","react-dom":"^16.3.0"},"devDependencies":{"@commitlint/cli":"^7.5.2","@commitlint/config-conventional":"^7.5.0","@types/jest":"^24.0.5","@types/node":"^11.9.4","@types/react":"^16.8.2","@types/react-dom":"^16.8.0","commitizen":"^3.0.7","cz-conventional-changelog":"^2.1.0","dotenv":"^6.2.0","husky":"^1.3.1","jest":"^24.1.0","prettier":"^1.16.4","react":"^16.8.4","react-dom":"^16.8.4","react-testing-library":"^5.8.0","ts-jest":"^24.0.0","typescript":"^3.3.1"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"gitHead":"1d93fecdf93032a012acc41c8f58221ec6a18dd1","_id":"react-model@2.4.1","_nodeVersion":"11.10.1","_npmVersion":"6.7.0","dist":{"integrity":"sha512-2Xiw4jiRMkQrpbZ4DGEcd8zTHRyt7SfhnpAhFQcAsojdQjzmaktQ36KmyFxFs2TPQPZaLr6SjOdv2cLeqEFMug==","shasum":"affe1ca233dfccbe820f2c8addf93632c22fc7f7","tarball":"https://registry.npmjs.org/react-model/-/react-model-2.4.1.tgz","fileCount":14,"unpackedSize":54401,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJch1VtCRA9TVsSAnZWagAACNYP/0vz+oZThixhVjy4EBx+\ns+UxNpkU2nIyUz7Hj27hUgX0ym+qAjAC94LoYgt3PHlNpd9rIS+WT5heXobv\na2nbpXRbkL2UfEfKTXtCtnHfVqQ0yVhCIcdS22rOMmBOVKiL0xx/yfoD0g/8\nq0Ddo45yZNSnUt2TCCCB/3tiil48eIdTfRtzkSvBil/HJp/ukoPYYDZMZnab\nzwi2NYdOjG3XwDesYRZdVci5vHL94QzxH8EN00lD9p4EhVhC2ICgdvc6gKl0\nOrauTdMYtXeR14O4SzxUl+gYC3LDPN27CLK46h+sTB2La0VwWAtllWcVz1Ax\nNBOoAks/cYAV1DVX8EEt4GTCZOvCnlJiabHiG38FTNTCMmGI5qD4lcJyX9Ec\nL/tOMyWNIR9BmFmFTqQvB3jnXB5mksb/36t+kIp5ymif1g83cnW3EdjKT1ay\nCA6/JFUR87QxR0z+LW7lZdMAyRF28BHz5z54MjIslfL/ONG/30ELokBNCtAN\nH8+lMpRRqGnMJ/mg/TD5UUNySgNzdaS2AbE8S2V28a0L83+fxpEGQ7mDrKw/\nFxNx+n0Sx9cJlzKP8XAfV4ht8Wso3Jk0hhCB9aCbR2pnRhxwH2+NYG2VcGT7\nwHWc/Fpgc5jUXIVwrVnaPJzZNReD6923lSZSBdV9O7KYVFsLEvrTlAlFE7i+\noZrF\r\n=lkGg\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIFJ1miCPYpvwdOJkLajlAsMXbfxMk5ZGPL66fJC81g9/AiA/jIzX4FG814W5pKn3MwD6qdJPbVNgN+PmJNGohgY39g=="}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_2.4.1_1552373100633_0.5316461903051075"},"_hasShrinkwrap":false},"2.4.2":{"name":"react-model","version":"2.4.2","description":"The State management library for React","main":"./dist/react-model.js","module":"./dist/react-model.esm.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false","commit":"git-cz","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^2.0.0"},"peerDependencies":{"react":"^16.3.0","react-dom":"^16.3.0"},"devDependencies":{"@commitlint/cli":"^7.5.2","@commitlint/config-conventional":"^7.5.0","@types/jest":"^24.0.5","@types/node":"^11.9.4","@types/react":"^16.8.2","@types/react-dom":"^16.8.0","commitizen":"^3.0.7","cz-conventional-changelog":"^2.1.0","dotenv":"^6.2.0","husky":"^1.3.1","jest":"^24.1.0","prettier":"^1.16.4","react":"^16.8.4","react-dom":"^16.8.4","react-testing-library":"^5.8.0","ts-jest":"^24.0.0","typescript":"^3.3.1"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"gitHead":"8a44981a7ce749a4021b672807fd3aea2a95124d","_id":"react-model@2.4.2","_nodeVersion":"11.11.0","_npmVersion":"6.7.0","dist":{"integrity":"sha512-CJRZPvtyC70LYML2BvGVE1lM3jz+/xEw4bImpfBV75t3urPHwrBPx8jyIv3Dqjr+i2/cqjM2ZKuGlWUmKP6RBQ==","shasum":"bcf66a12760d97af9d4fca7b65635505a7d35481","tarball":"https://registry.npmjs.org/react-model/-/react-model-2.4.2.tgz","fileCount":14,"unpackedSize":55367,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJch2vBCRA9TVsSAnZWagAAXV0P/18Z+e/wZuH6p6xptorr\n1fvWhVFH7W5SA7DXcRdunmuubBBkMqFwcc8yqrvZyrFCjTHK4HEwmEvvkRgE\n6aRZ7aosm8cPljB0RrEjN1QGeJGLS+vZ7KfVWEbw15VHpuxeBOnHNdn6KHqT\n1A/ZYjOmnIAEFog8Fei/IPj8fqnOgKSYW9cj2HO+RaXV25qAXJO9/BztVcAK\nHJv3gGXT3xXWVaoocfk2KE2yq3YI6HOXgFsFnmWbOCmBMSsqU7t1aWDFEP6n\nGcRrSued8FxT+9UjeqxjOUKiaDtPj+nkyFdjmaAAm6N/Why/vSv4pbpl4qc1\n4Mg4kYXopI15KX8Tnr4Ogc5LAO/87mruYoyCyFbp8nnRgrmGbBt9n4xUHdL6\nQBHjHKEa0IDBQPrKptpwdrg6OEv6sj8/qwgBTQ53JeLouHQJFpJhwpM8MCUy\nu6um6xVPetKv01EQGsQrjwLvUEkbvBWDYLI8ZobGsttDdnPrFHiay4L4Ur9J\nO8HP/f5xbePCJL6hx0YD3us7bnWEH8wE4lG1994t9SOEbctSHVh808oYSXW3\nW44KrH5Et2fz7QLEmjNgJiUCEbiB4v3+T54cL2xWMhT0vKVWH0+N+B/MvY24\nL/mz7dUH98zNoYsnqDja4leOKJmL7Na5JGk10/R88RLvl0qkpokKGDDlRMJK\nPxs9\r\n=JGpa\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDqLd06a+Aw2ysUrYleEACU38ofz8fgiH/+O6fhtSSBlgIhALpDeAVWUTPXE4hWwI5s7RysDZFMpeACt3rGjkjJnlm7"}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_2.4.2_1552378816959_0.08089539611787244"},"_hasShrinkwrap":false},"2.5.0":{"name":"react-model","version":"2.5.0","description":"The State management library for React","main":"./dist/react-model.js","module":"./dist/react-model.esm.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false","commit":"git-cz","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^2.0.0"},"peerDependencies":{"react":"^16.3.0","react-dom":"^16.3.0"},"devDependencies":{"@commitlint/cli":"^7.5.2","@commitlint/config-conventional":"^7.5.0","@types/jest":"^24.0.5","@types/node":"^11.9.4","@types/react":"^16.8.2","@types/react-dom":"^16.8.0","commitizen":"^3.0.7","cz-conventional-changelog":"^2.1.0","dotenv":"^6.2.0","husky":"^1.3.1","jest":"^24.1.0","prettier":"^1.16.4","react":"^16.8.4","react-dom":"^16.8.4","react-testing-library":"^5.8.0","ts-jest":"^24.0.0","typescript":"^3.3.1"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"gitHead":"884366568d9bed69f753d7934d879bf5ab2eb43b","_id":"react-model@2.5.0","_npmVersion":"6.4.1","_nodeVersion":"10.15.0","_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"dist":{"integrity":"sha512-zRRzwvgL1OG2sLPM0erap1a9kCzYF9HuBpkM5rJG5IzVEm7vBmBkgS+qY/g6iNOsH+57ssmMCiLAYS9Ons460g==","shasum":"e69e8d30461e3610dee0ac26b85872368cc8b4dc","tarball":"https://registry.npmjs.org/react-model/-/react-model-2.5.0.tgz","fileCount":14,"unpackedSize":56965,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJch7U+CRA9TVsSAnZWagAADwIP/Rw/20YQEvyAHy2vBhGz\nKxOagZ33cZfwN+GNZeNpq8zzc1CxCdlAAcViq06LkU2l6xT69Aw8A6jYqv4m\nal6Gu6hB3KrcvmiGignGOJjLivXOcnx9F0O69GCUTd+FbShTEp4wF3zomJLt\nBSlkZDf+nud/n/U0spZ2h0e9EFoSUyRKyWBbts/Q6ZZ9Gu+Z8C5s54DQzu8s\nUrxKvoiorLpqqRzqEKT8pMrPlYOXb7yBO+kNIEaZyiGjADGfuYIVuVy2Kkv0\nPpu7yk67nk+fmhQgIZTYTXaVpf8ePymtF2RJQE/VfBsuyLVHSge8qiiVW9nF\n3P0qp3kOkheGyWzceap+5z7EF8mtmefvmPeCt8erMDx6dM2NXTe0tvgkvwQO\nH8fLE4x7GrEtDAXSJqqMvRekEHd8ekEn4NAttBf6KH9WQO5rTuEOVqKSO8MD\nIgvpk9gZkx8sTbJNThstR2Wel+OT+xQp1UjnXKsbzTMf2FWo0QqKTyoX3aOI\nlfWwEsa7U6QZKknsD9bFPeNtustdBDBDjPE9FaKJqVtNa9WwyjbOo1hrxICL\nEA3y25welpVeYvgl7m3oNeYc5Gv4I+Ydk8yJ6W5WlsqGCpmOKhF2Ec+tfxfZ\nxjtoxOw2zyOCUYZ77+NPUeM7ssz3KuxeNLlOLQO3c4Vo9YCh/rMqIMJTxRmD\nPZPH\r\n=BB5y\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCICL5CGbDvpf5i/WUkXKoJ/TsLmaGv8ARfnnuLiLqeQPbAiAhxbEH4jQYpLOIdxdLuPAy7PFJnqPuuFSoriaPkM0doA=="}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_2.5.0_1552397629981_0.08590713110015757"},"_hasShrinkwrap":false},"2.5.1":{"name":"react-model","version":"2.5.1","description":"The State management library for React","main":"./dist/react-model.js","module":"./dist/react-model.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false","commit":"git-cz","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^2.0.0"},"peerDependencies":{"react":"^16.3.0","react-dom":"^16.3.0"},"devDependencies":{"@commitlint/cli":"^7.5.2","@commitlint/config-conventional":"^7.5.0","@types/jest":"^24.0.5","@types/node":"^11.9.4","@types/react":"^16.8.2","@types/react-dom":"^16.8.0","commitizen":"^3.0.7","cz-conventional-changelog":"^2.1.0","husky":"^1.3.1","jest":"^24.1.0","prettier":"^1.16.4","react":"^16.8.4","react-dom":"^16.8.4","react-hooks-testing-library":"^0.3.6","react-testing-library":"^6.0.0","ts-jest":"^24.0.0","typescript":"^3.3.1"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"gitHead":"912d5ffd9c360c42c94bb032b093e26ed4199df2","_id":"react-model@2.5.1","_npmVersion":"6.4.1","_nodeVersion":"10.15.0","_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"dist":{"integrity":"sha512-e9AJGwikYUwZUv+tWQcHx1K+MdgfShAY+0mas5y2yggq1fGc8BCzRocSd02L9wmNjuzHUavouM5VM2heBsfWLg==","shasum":"8e28673fa6cb18cd3892f7d5e4e60139e6b6bb21","tarball":"https://registry.npmjs.org/react-model/-/react-model-2.5.1.tgz","fileCount":15,"unpackedSize":57811,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJci4IYCRA9TVsSAnZWagAAr9MP/iwRuYlLZuEYTlnWaw9N\nebMXmdJFjyeU2TxPrgbIOm4wiTNg/EsGfg0ze9EGECA4U/fxFe39MHnxOTOa\nbjRm6w5xwiH5jGDNlwhF/g4zlTg4TGlhQbHh8NxvWqm+ubxdK+jbC6ZKTpbj\nHzPWNrmK+DgWEpfx/UPqPUCRrb2tipylSzdtLYiK+ZkNHVRNapngGVYN5i1f\natrMtMp67ryHFPaDdgaoKK4iiwaIsEelaklCTxgsfHd9y+ZhhtSALgtE+Wg4\nV3lvqSTK08XJ+7m08tKis5UN4CbjKTXjdEWtIg2WQwRPX9DNJ5YH9DzCl7Ef\n7scvyrFhckLwjpJS5/uYAwvHKp3OKlCKCwv6X9ytGL/DDn6is5ePWgQEftBl\nVZH8Yqvsm5EM76O9wqT0xLFfGuce2GoFtkJZQPTEogu/PPRBFtUP8/xZjqqV\nDCwQ558JZ0IF687jC7MjmC0rmCYYOwgZwkIIwwyLKeSHmKeNs6K7xxEDwRxQ\nmskXDpbL17pLcvHzecpZKTagUZxlNMAhBHgH0JNn6iXbNmuYq4W52Y48q2Tn\nSFQ7BmCt0T65FesNJxNCQR96Oqg0c6ofvwFf+SZ30vh9F+XRdfMMfSdFK9Lt\nfmIQTB+E5lHBP1cYBxmULPLDEejoPdxc2nMev+tp5vPTNPSNC3ic6Injvxxz\n20P3\r\n=WcDl\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIFGLxPAK5qmFcQp37x4Ze5GkmT06grppi+5i0+lfF3CyAiB+MQ/rOZSKR16MNRyAhxDJrHol4R0U31w82iKuylvVMg=="}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_2.5.1_1552646679806_0.19983510041999009"},"_hasShrinkwrap":false},"2.5.2":{"name":"react-model","version":"2.5.2","description":"The State management library for React","main":"./dist/react-model.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false","commit":"git-cz","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^2.0.0"},"peerDependencies":{"react":"^16.3.0","react-dom":"^16.3.0"},"devDependencies":{"@commitlint/cli":"^7.5.2","@commitlint/config-conventional":"^7.5.0","@types/jest":"^24.0.5","@types/node":"^11.9.4","@types/react":"^16.8.2","@types/react-dom":"^16.8.0","commitizen":"^3.0.7","cz-conventional-changelog":"^2.1.0","husky":"^1.3.1","jest":"^24.1.0","prettier":"^1.16.4","react":"^16.8.4","react-dom":"^16.8.4","react-hooks-testing-library":"^0.3.6","react-testing-library":"^6.0.0","ts-jest":"^24.0.0","typescript":"^3.3.1"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"gitHead":"912d5ffd9c360c42c94bb032b093e26ed4199df2","_id":"react-model@2.5.2","_npmVersion":"6.4.1","_nodeVersion":"10.15.0","_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"dist":{"integrity":"sha512-YJ3MDK516/MyD3PRFNM3UdrK4wFIMAHTwZAdDVBkKk73YTJPvuD2nLabwFVD/ej2P3GdjCSuUb5MOxKjVjf+kw==","shasum":"ceec76e528ecbf0604fe4be5fbec72fa414ec5da","tarball":"https://registry.npmjs.org/react-model/-/react-model-2.5.2.tgz","fileCount":16,"unpackedSize":65821,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJci4VlCRA9TVsSAnZWagAATkEQAJxmGqdpzQsp+9KZB/T8\nCojSlVgoPeKG005BRd6sEOtZI3c5QDGenOGRQyKsl7EsycE1xV99gW6gjqQz\nk8w2svVty8GlNZwDaO7h3YIZtvFK/2CQtCDPxs8DXQ79jLZ/0qcnhnHJWGJO\nQir3NqEFFXOQDGxOhnK72jAtDGc8YTnFhqRo8C9AA1jpMvGxLsgn7RIWF50q\nWH0PMRO2SalRFjJtQErPlFSMm5Aqlv59rIwkeKfXOV+lWjjikUyB/Q8f891/\n7fS0yAuTVOCjcamxj6XHmvWU8Ek1W1IlxVZvZ2ZMH2UvM9+oU29dCupHn8lj\nZAH8h77zCIZ7MeW7+Q6AVb2cWumpDLyABxWH3Ziizlg6b9GID7afshE1FcqN\nKYEIKBYt14C6lf52gpFWkYLPrcEn+BMymb4svQ9W2mId2S5xOszBhETZ8Xo9\n4ml/KVyUwdq3CysaodabaLRZEaG4u0dPOfvq0XBZVKF83wRaNYLCllNfpMtM\n7gZD2Teo8/dzkTE87S/ypCy8tLaWbDoKUZc9W4pR8JhRDCGvWzl6at4iK6qt\n/mO21yt9geGgqPjt6H8KtEM8fwG5q8hWSf5+cqb5zsG5b54P+lUgOVyp4X4U\nFJ3jGA2XL4ISx1FyfpEaAuVdN/zmE4YbLzYirslBniXDgb3iB/mILiZ7d/wP\ngFCf\r\n=/a2b\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCjMLiSyWSqytLv75Vi87Zr3s0P5sRTuPtqytpYQVepeQIgYE1dEJwyhSCwK0+5SZbQmzX+v0rrLHlvZbpJmPJYBWs="}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_2.5.2_1552647525161_0.6950662397364082"},"_hasShrinkwrap":false},"2.5.3":{"name":"react-model","version":"2.5.3","description":"The State management library for React","main":"./dist/react-model.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false","commit":"git-cz","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^2.0.0"},"peerDependencies":{"react":"^16.3.0","react-dom":"^16.3.0"},"devDependencies":{"@commitlint/cli":"^7.5.2","@commitlint/config-conventional":"^7.5.0","@types/jest":"^24.0.5","@types/node":"^11.9.4","@types/react":"^16.8.2","@types/react-dom":"^16.8.0","commitizen":"^3.0.7","cz-conventional-changelog":"^2.1.0","husky":"^1.3.1","jest":"^24.1.0","prettier":"^1.16.4","react":"^16.8.4","react-dom":"^16.8.4","react-hooks-testing-library":"^0.3.6","react-testing-library":"^6.0.0","ts-jest":"^24.0.0","typescript":"^3.3.1"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"gitHead":"912d5ffd9c360c42c94bb032b093e26ed4199df2","_id":"react-model@2.5.3","_npmVersion":"6.4.1","_nodeVersion":"10.15.0","_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"dist":{"integrity":"sha512-LFStg3AmWicPPm8u/Xikvf1M3VszbB+OtWnoMcs/4Aw92abuWPvdQ5hp7Ag4Q/UnIkuThtwoj+B0AZPlvJiAtg==","shasum":"b03966ab8d388a6bf874b5543dd3829fb2abba10","tarball":"https://registry.npmjs.org/react-model/-/react-model-2.5.3.tgz","fileCount":15,"unpackedSize":58227,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJci4XACRA9TVsSAnZWagAAyOMP/AhtfLW8X1NGnL98A9fA\n8A6qzBspQJYd/mhgpoLZ2hjm+RmQ1tiQE1etC93p4qESHRuJri0n9a4/+LNa\n2+2/oxOqgvSk70as/alcX+9RzcmAaOcbYI3HMuh8uzCBLp6lokIqecSw0IOG\nqylQtzRXqNmmrAW87VIhRGVlCY5RzSGFc/GcNS8V6ije79U6TFU95H4FN25o\ng80kEqHiuUksdbB9V7EvoFzsbDhzzU5lm3k668+c86tJHnz8w/svjnA/Xw3k\nQrujsFGCeLFCwhdPWauIp7aB9G8LNXV2zzjNYdI7yMpid4v1CjI/Llh27A7p\nQ14saYsn0z8fCXk3ER4lzVMsVDlt0kSGlsOf0WwahDTKj9E92uUxz4DpyjKY\npW2yR/HsS/+uD+2ya/r23MNls4v5ajntCZJbunUBVKIbUofNwsqco5dQv6qU\n44dk6n1fnvJL7vf/rOorO+dKOSEaWJr7KUQTqUyctXAYvGrJMXghuP9ipQ1n\nAkp5rPJFnPkFIvHM6rNrDxqBxvSAzLGtnj+IM+rHAbVZ68zMf1TUzkss+JeB\nlKZIsIrYy9eOzxWAla+4i4oZJ4BpN/MEc+hN77hrDefc5c0kQQZhA8rZORhQ\naP5eYi1IcmT53Up7wG50DbLeUemXo39WmzY2GBO54TO3M9c6P+ZrUux/ACta\ns36O\r\n=k04l\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIAeLjy6jcGOLDahVAe27pGew9DYF232y8SCMAheRQc2lAiEA4DkuYd3ef6aEClxYMZ2uLCV9ATbieau4desFQ+jyuPo="}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_2.5.3_1552647615451_0.9404839578789581"},"_hasShrinkwrap":false},"2.6.0-alpha.0":{"name":"react-model","version":"2.6.0-alpha.0","description":"The State management library for React","main":"./dist/react-model.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false","commit":"git-cz","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^2.0.0"},"peerDependencies":{"react":"^16.3.0","react-dom":"^16.3.0"},"devDependencies":{"@commitlint/cli":"^7.5.2","@commitlint/config-conventional":"^7.5.0","@types/jest":"^24.0.5","@types/node":"^11.9.4","@types/react":"^16.8.2","@types/react-dom":"^16.8.0","commitizen":"^3.0.7","cz-conventional-changelog":"^2.1.0","husky":"^1.3.1","jest":"^24.1.0","prettier":"^1.16.4","react":"^16.8.4","react-dom":"^16.8.4","react-hooks-testing-library":"^0.3.6","react-testing-library":"^6.0.0","ts-jest":"^24.0.0","typescript":"^3.3.1"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"gitHead":"bb92778ea6f7b8bb65f310d7bc4d2065e553e312","_id":"react-model@2.6.0-alpha.0","_npmVersion":"6.4.1","_nodeVersion":"10.15.0","_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"dist":{"integrity":"sha512-oucnTbvwJrtGryT3Xd6xZ2TG/RmHLDuvqlVBySiBVB4/lp8qbLftMjmjIBVGZoRw7El+hXq76c1TSYHwGtSc2A==","shasum":"cb6088e3213c706cd073c4716f1e7796272ae75c","tarball":"https://registry.npmjs.org/react-model/-/react-model-2.6.0-alpha.0.tgz","fileCount":15,"unpackedSize":56251,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJclO1pCRA9TVsSAnZWagAA18QP/RyDgABiVKpWeEnNh7m3\nFNEVd1Yi8miOp3D9SRvFg7OMzOg5UrEnJPH0z7i93ifpC2f9lXSgjRNfpS44\nD2rQyI1ynqj/siHz4M3ngFBr1dWuFHfsXotE5wGuGMqMWDDk4+ClusNiQ9WY\nhhl/8aX6qvGYr0ObS0pTMEjzZMp/HieMkCltP/uEGAq+YH9QCo9ZAI/NnFrs\nSUFsvrKJrm2bhtaJmPT2cC2olf4aCWG69XsZmYBHRMXTJKyEn6NNEiTABooj\nCqht645IqOc0r5S6awkOZ+PdDTuyi1bftzI2ZWqIa8WbYvYvxK290LWwnKP9\nZ2kt0fDvLf3L+lWjN69XFx1rqZkVqhd5D27Qf9lLOUv66D63z8S0FrNi5uv9\n4pSoKsxjJv+nuJJX5TcPtOK8SbxMV75z61V3rlFy/XUwC0AL9Xd728i/aV5J\nxk/zX4KFFXt+3vSrfNMy8EQsY9rNI8qwx3Ll2/lT0NN4IwoFYd/MWkxv3dE8\nkrx1CcTOjglCf9cKTWTYRUImjIMzk6bMc+MZxho3QDNOllH757fv1ynMjUh8\nLh45TEW4xENW2KxJzHaYxP7O9k1OyYgGyRL1ioYQVCYRNnY+caxiurH0kEod\nzK5dbye0d3o7Kc0jLRKdwPFL/KYlkLDRgmV2eG1HaOOpakQeGw4m9a4gKdex\nNda5\r\n=mKZD\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQCag7rwvZBHT/h6yzAtopgBO0K5iianonKTGP7TZd9NfQIhALJrigsnDeN0VNwOtJQyaDM0e2GHGYyo82WnLhJewdOE"}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_2.6.0-alpha.0_1553263977011_0.908587685982013"},"_hasShrinkwrap":false},"2.6.0":{"name":"react-model","version":"2.6.0","description":"The State management library for React","main":"./dist/react-model.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false","commit":"git-cz","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^2.0.0"},"peerDependencies":{"react":"^16.3.0","react-dom":"^16.3.0"},"devDependencies":{"@commitlint/cli":"^7.5.2","@commitlint/config-conventional":"^7.5.0","@types/jest":"^24.0.5","@types/node":"^11.9.4","@types/react":"^16.8.2","@types/react-dom":"^16.8.0","commitizen":"^3.0.7","cz-conventional-changelog":"^2.1.0","husky":"^1.3.1","jest":"^24.1.0","prettier":"^1.16.4","react":"^16.8.4","react-dom":"^16.8.4","react-hooks-testing-library":"^0.3.6","react-testing-library":"^6.0.0","remark-cli":"^6.0.1","remark-lint":"^6.0.4","remark-preset-lint-recommended":"^3.0.2","ts-jest":"^24.0.0","typescript":"^3.3.1"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"gitHead":"cbc62d271b1ac5d583e8ae519c0b97cc52becbf4","_id":"react-model@2.6.0","_npmVersion":"6.4.1","_nodeVersion":"10.15.0","_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"dist":{"integrity":"sha512-AqGV6zbz6F7c5yREgbPLqdE4d3myTDsg6HDk6ua2l3F/AqbpIZeb7ypViMc7ycTHyVvEmlhS1h0bIUxJ1E2Rfg==","shasum":"21cd8c8b51b41e2e87c63d1f4bdd090326efbf03","tarball":"https://registry.npmjs.org/react-model/-/react-model-2.6.0.tgz","fileCount":20,"unpackedSize":63783,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJcmyTnCRA9TVsSAnZWagAAji4P/j8Ifhh0wxM7g+0k2TgV\nURqcBKkbxtqBbSfGBi0TlD+nbd356bqRBobzgDGkWWyY9gENV15WRkCV33Lg\nge4p2A+eKtH4lS84wUneIzsgTnXIFdo1crj9C+M7pK6QzrUX3cXJjD/oE8LF\nbQa0B0jLsImA7WFnNqamJj5lkKd2P3n0y9/f/8if3/EtiwxjrjJCu5N4lQgd\n/7a8v3nHR4q0puB+UY11vcgbhTKtQ1JBixkmeVypy4Xw0TKXRBBzpjjI0O3E\ncR1evm1C+JGGosG0TbxLHKNUSCzaRpY8Dz2nAaQshsJrE+qd4oTtH/vs+J0d\nKvINXzpo4X8daM5yoToznjZ7W01INjk6gCaYayQs++FIXWnKAucfnvYx2XA6\nGCnpkbs4vqDLlebFXWXGVmoKdB7gUtSTGrHWfFIL1NpYKmy577HDw6CG1S47\nBYQxQBs0/2HAy/60mf8JhRZH0uzIXyNBXH29W1nEhrkavOqdR6/4x3FWGlNO\nMBhnHGnweeHkW0sDYHZ7lmojeNawmTITS+GbLVCbt5juSH6vOIMFuU4XOxH2\nTlKIqeC+zXRt598ijBO1KMicTBjkvuuKnOqv+IaSSV7lK5RRYDCf6w3gVei+\n8c981W3oj2HJ0oFFMR+xwGgQlCaBMcFEWtl1SLxvALCjTBs+MsT2hp3ifqss\n7tOB\r\n=RyqB\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIAKmFGNfzBuefy6KfnEhmGL9lJWEkRaWm5dpoTxN2HTnAiBTxFpHiPDhnSqE37naoxCeZxY77ONPJkoKUlq0XZD1Ew=="}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_2.6.0_1553671398971_0.22087487326135502"},"_hasShrinkwrap":false},"3.0.0-alpha.1":{"name":"react-model","version":"3.0.0-alpha.1","description":"The State management library for React","main":"./dist/react-model.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^2.0.0"},"peerDependencies":{"react":"^16.3.0","react-dom":"^16.3.0"},"devDependencies":{"@commitlint/cli":"^7.5.2","@commitlint/config-conventional":"^7.5.0","@types/jest":"^24.0.5","@types/node":"^11.9.4","@types/react":"^16.8.2","@types/react-dom":"^16.8.0","commitizen":"^3.0.7","cz-conventional-changelog":"^2.1.0","husky":"^1.3.1","jest":"^24.1.0","prettier":"^1.16.4","react":"^16.8.4","react-dom":"^16.8.4","react-hooks-testing-library":"^0.4.0","react-testing-library":"^6.0.0","remark-cli":"^6.0.1","remark-lint":"^6.0.4","remark-preset-lint-recommended":"^3.0.2","ts-jest":"^24.0.0","tslint":"^5.14.0","typescript":"^3.3.4000"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"readme":"# react-model &middot; ![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) [![Build Status](https://travis-ci.org/byte-fe/react-model.svg?branch=master)](https://travis-ci.org/byte-fe/react-model) [![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⚔ Fully TypeScript Support\n\n📦 built with microbundle\n\n⚙️ Middlewares Pipline ( redux-devtools support ... )\n\n☂️ 100% test coverage, safe on production\n\n🐛 Debug easily on test environment\n\n```tsx\nimport { Model } from 'react-model'\n\n// define model\nconst Todo = {\n  state: {\n    items: ['Install react-model', 'Read github docs', 'Build App']\n  },\n  actions: {\n    add: (s, actions, todo) => {\n      // s is the readonly version of state\n      // you can also return partial state here but don't need to keep immutable manually\n      // state is the mutable state\n      return state => state.items.push(todo)\n    }\n  }\n}\n\n// Model Register\nconst { useStore } = Model({ Todo })\n\nconst App = () => {\n  return <TodoList />\n}\n\nconst TodoList = () => {\n  const [state, actions] = useStore('Todo')\n  return <div>\n    <Addon handler={actions.add} />\n    {state.items.map((item, index) => (<Todo key={index} item={item} />))}\n  </div>\n}\n```\n\n---\n\n## Quick Start\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\ninstall package\n\n```shell\nnpm install react-model\n```\n\n## Table of Contents\n\n- [Core Concept](#core-concept)\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- [Other Concept required by Class Component](#other-concept-required-by-class-component)\n  - [Provider](#provider)\n  - [connect](#connect)\n- [FAQ](#faq)\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\n## Core Concept\n\n### Model\n\nEvery model have their 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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (state, _, params) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    },\n    openLight: async (state, actions) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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<typeof Model.actions>\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 keep the state and actions in a separate store. So you need to register them before using.\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 stores = { Home, Shared }\n\nexport const { getInitialState, useStore, getState, getActions, subscribe, unsubscribe } = Model(stores)\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 return from useStore can invoke the 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 getActions now.\n\n```tsx\nimport React from 'react'\nimport { useStore } from '../index'\n\n// CSR\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button onClick={e => actions.increment(33)}>home increment</button>\n      <button onClick={e => sharedActions.increment(20)}>\n        shared increment\n      </button>\n      <button onClick={e => actions.get()}>fake request</button>\n      <button onClick={e => actions.openLight()}>fake nested call</button>\n    </div>\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 with depActions and React.memo to prevent child components rerender frequently.](https://github.com/ArrayZoneYour/react-model-todomvc/blob/master/src/components/TodoItem.tsx)\n\n[Demo Repo](https://github.com/ArrayZoneYour/react-model-todomvc)\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> Hint: The state returned should only be used as readonly\n\n```jsx\nimport { useStore, getState } from '../model/index'\n\nconst BasicHook = () => {\n  const [state, actions] = useStore('Counter')\n  useEffect(() => {\n    console.log('some mounted actions from BasicHooks')\n    return () =>\n      console.log(\n        `Basic Hooks unmounted, current Counter state: ${JSON.stringify(\n          getState('Counter')\n        )}`\n      )\n  }, [])\n  return (\n    <>\n      <div>state: {JSON.stringify(state)}</div>\n    </>\n  )\n}\n```\n\n[⇧ back to top](#table-of-contents)\n\n### actions\n\nYou can call other models' actions with getActions api\n\ngetActions 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: () => {\n      actions.Shared.changeTheme('dark')\n      actions.Counter.increment(9)\n    }\n  }\n}\n\nexport default model\n```\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 = () => {\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (s, _, params) => {\n      // issue: https://github.com/Microsoft/TypeScript/issues/29196\n      // async function return produce need define type manually.\n      return (state: typeof s) => {\n        state.counter += params || 1\n      }\n    },\n    decrease: (s, _, params) => s => {\n      s.counter += params || 1\n    }\n  }\n}\n```\n\nJavaScript Example\n\n```js\nconst Model = {\n  actions: {\n    increment: async (s, _, params) => {\n      return state => {\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<details>\n<summary>Store: shared.ts</summary>\n<p>\n\n```ts\nconst initialState = {\n  counter: 0\n}\n\nconst Model: ModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: (state, _, params) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    }\n  },\n  // Provide for SSR\n  asyncState: async context => {\n    await waitFor(4000)\n    return { counter: 500 }\n  },\n  state: initialState\n}\n```\n\n</p>\n</details>\n\n<details>\n<summary>Global Config: _app.tsx</summary>\n<p>\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) => {\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    <Container>\n      <Component {...pageProps} />\n    </Container>\n  )\n}\n\nMyApp.getInitialProps = async (context: NextAppContext) => {\n  if (!(process as any).browser) {\n    const initialModels = context.Component.getInitialProps\n      ? await context.Component.getInitialProps(context.ctx)\n      await getInitialState() // get all model initialState\n      // : await getInitialState({ modelName: 'Home' }) // get Home initialState only\n      // : await getInitialState({ modelName: ['Home', 'Todo'] }) // get multi initialState\n      // : await getInitialState({ data }) // You can also pass some public data as asyncData params.\n    return { initialModels }\n  } else {\n    return { persistModel }\n  }\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Page: hooks/index.tsx</summary>\n<p>\n\n```tsx\nimport { useStore, getState } from '../index'\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button\n        onClick={e => {\n          actions.increment(33)\n        }}\n      >\n    </div>\n  )\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Single Page Config: benchmark.tsx</summary>\n<p>\n\n```tsx\n// ...\nBenchmark.getInitialProps = async () => {\n  return await getInitialState({ modelName: 'Todo' })\n}\n```\n</p>\n</details>\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<{}> = async (context, restMiddlewares) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: any) => 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) => async (params: any) => {\n  const context: Context = {\n    modelName,\n    setState,\n    actionName: action.name,\n    next: () => {},\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## 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      <Provider>\n        <Counter />\n      </Provider>\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<details>\n<summary>Javascript decorator version</summary>\n<p>\n\n```jsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\n\nconst mapProps = ({ light, counter }) => ({\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      <>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(5)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n      </>\n    )\n  }\n}\n```\n\n</p>\n</details>\n\n<details>\n<summary>TypeScript Version</summary>\n<p>\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) => ({\n  lightStatus: light ? 'open' : 'close',\n  counter,\n  response\n})\n\ntype RType = ReturnType<typeof mapProps>\n\nclass TSCounter extends PureComponent<\n  { state: RType } & { actions: ActionType }\n> {\n  render() {\n    const { state, actions } = this.props\n    return (\n      <>\n        <div>TS Counter</div>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(3)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n        <button onClick={e => actions.get()}>Get Response</button>\n        <div>message: {JSON.stringify(state.response)}</div>\n      </>\n    )\n  }\n}\n\nexport default connect(\n  'Home',\n  mapProps\n)(TSCounter)\n```\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\n\n## FAQ\n\n### How can I disable the console debugger\n\nJust remove consoleDebugger middleware.\n\n```typescript\nimport { actionMiddlewares } from 'react-model'\n// Find the index of middleware\nconst consoleDebuggerMiddlewareIndex = actionMiddlewares.indexOf(\n  middlewares.consoleDebugger\n)\n// Remove it\nactionMiddlewares.splice(consoleDebuggerMiddlewareIndex, 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) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: Error) => 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","readmeFilename":"README.md","gitHead":"0b0aedfe87fff180127f8cd197d321400486a57f","_id":"react-model@3.0.0-alpha.1","_nodeVersion":"10.15.0","_npmVersion":"6.9.0","dist":{"integrity":"sha512-HGAU3vMZ+77JPOPNPPgbIElnElpFiul3h992zcITl7i9DYDemVdT0ghRMa2CIRn6DoJtUORcNlP8L/y4lMSHHA==","shasum":"9f72ee1d0275957a3e1686c2ba2b14e017f695d4","tarball":"https://registry.npmjs.org/react-model/-/react-model-3.0.0-alpha.1.tgz","fileCount":27,"unpackedSize":70332,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJcpIboCRA9TVsSAnZWagAAkM0QAJI/6zaUB2x/AMMcSShO\ntM78qXN8mYT8nGPdtkEOWhY99cYlNOQpIWaZKa9Fwxjx7uUK4jkJNkLNdj8d\nvkEAGJRS5unbS8LAFAAU7pEas8yFFBxUShUJ5q6T3APVFYTL5GUT+0luHR+H\nI7VelmqEe4s5xF0UGPKiUxoYok2d5yP+ftjVmo58kx66ekTupKevL4cg940m\n/WpcXqx8vAxG0JpFiN6stbO4975xPQwnL7dkJjVFi8MV3umJiroAwSBYQVvZ\n7zzLlpGSuuJZMBlGiYvAh02XbZseR8W4ZOQtmsomuzzjGu5D1Nzz1GSgQe+G\nmn6btZ41h8HuTQmjDAhNP0UgSc3QdCXQs3R1yUNdPHMPqi4qAOFhIgdmjdHi\nQnm2E/DJAXEs5+revAp9VMKuQogAjXI1gN0fQZgh+lOEv8x7F5ffen8IwwcU\nnJlI2MCkk+37xmgiK6eEdLE2hFm9GlrokMb2Jkl0+vw4e7WCSaGVka9WE39w\n/xGPCLhwQTGx8wzAI20RDNxcV16nN6gksRxuUUmmA+euP9fPE1UCzoMg9rsG\nrYCKGMB5HJuGUhUpbQWXQgUvDwPDttT5QPOdrR+EzOnPbyIpCYo5MK/Q4eJw\nfk/lMHRyX6z8H8ZvSA4uQMyFIu8qU+oDkCmuZOfVYpFmWWrPdK3NvgVGrmaI\ntv5Y\r\n=TIKk\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQD4klnZlb4smNkcAwkyabUSGiRH35SrdeJY2s2EbeP58QIhAPkKNbVnUiToJ4LsacZGBTgjv6T+2avuXraQf+o4o4k6"}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_3.0.0-alpha.1_1554286311588_0.34789612666837244"},"_hasShrinkwrap":false},"2.7.0-alpha.0":{"name":"react-model","version":"2.7.0-alpha.0","description":"The State management library for React","main":"./dist/react-model.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^2.0.0"},"peerDependencies":{"react":"^16.3.0","react-dom":"^16.3.0"},"devDependencies":{"@commitlint/cli":"^7.5.2","@commitlint/config-conventional":"^7.5.0","@types/jest":"^24.0.5","@types/node":"^11.9.4","@types/react":"^16.8.2","@types/react-dom":"^16.8.0","commitizen":"^3.0.7","cz-conventional-changelog":"^2.1.0","husky":"^1.3.1","jest":"^24.1.0","prettier":"^1.16.4","react":"^16.8.4","react-dom":"^16.8.4","react-hooks-testing-library":"^0.4.0","react-testing-library":"^6.0.0","remark-cli":"^6.0.1","remark-lint":"^6.0.4","remark-preset-lint-recommended":"^3.0.2","ts-jest":"^24.0.0","tslint":"^5.14.0","typescript":"^3.3.4000"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"gitHead":"b79844a270c8b458e7b4e867bd3a1815ee6a3f41","readme":"# react-model &middot; ![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) [![Build Status](https://travis-ci.org/byte-fe/react-model.svg?branch=master)](https://travis-ci.org/byte-fe/react-model) [![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⚔ Fully TypeScript Support\n\n📦 built with microbundle\n\n⚙️ Middlewares Pipline ( redux-devtools support ... )\n\n☂️ 100% test coverage, safe on production\n\n🐛 Debug easily on test environment\n\n```tsx\nimport { Model } from 'react-model'\n\n// define model\nconst Todo = {\n  state: {\n    items: ['Install react-model', 'Read github docs', 'Build App']\n  },\n  actions: {\n    add: (s, actions, todo) => {\n      // s is the readonly version of state\n      // you can also return partial state here but don't need to keep immutable manually\n      // state is the mutable state\n      return state => state.items.push(todo)\n    }\n  }\n}\n\n// Model Register\nconst { useStore } = Model({ Todo })\n\nconst App = () => {\n  return <TodoList />\n}\n\nconst TodoList = () => {\n  const [state, actions] = useStore('Todo')\n  return <div>\n    <Addon handler={actions.add} />\n    {state.items.map((item, index) => (<Todo key={index} item={item} />))}\n  </div>\n}\n```\n\n---\n\n## Quick Start\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\ninstall package\n\n```shell\nnpm install react-model\n```\n\n## Table of Contents\n\n- [Core Concept](#core-concept)\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- [Other Concept required by Class Component](#other-concept-required-by-class-component)\n  - [Provider](#provider)\n  - [connect](#connect)\n- [FAQ](#faq)\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\n## Core Concept\n\n### Model\n\nEvery model have their 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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (state, _, params) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    },\n    openLight: async (state, actions) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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<typeof Model.actions>\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 keep the state and actions in a separate store. So you need to register them before using.\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 stores = { Home, Shared }\n\nexport const { getInitialState, useStore, getState, getActions, subscribe, unsubscribe } = Model(stores)\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 return from useStore can invoke the 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 getActions now.\n\n```tsx\nimport React from 'react'\nimport { useStore } from '../index'\n\n// CSR\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button onClick={e => actions.increment(33)}>home increment</button>\n      <button onClick={e => sharedActions.increment(20)}>\n        shared increment\n      </button>\n      <button onClick={e => actions.get()}>fake request</button>\n      <button onClick={e => actions.openLight()}>fake nested call</button>\n    </div>\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 with depActions and React.memo to prevent child components rerender frequently.](https://github.com/ArrayZoneYour/react-model-todomvc/blob/master/src/components/TodoItem.tsx)\n\n[Demo Repo](https://github.com/ArrayZoneYour/react-model-todomvc)\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> Hint: The state returned should only be used as readonly\n\n```jsx\nimport { useStore, getState } from '../model/index'\n\nconst BasicHook = () => {\n  const [state, actions] = useStore('Counter')\n  useEffect(() => {\n    console.log('some mounted actions from BasicHooks')\n    return () =>\n      console.log(\n        `Basic Hooks unmounted, current Counter state: ${JSON.stringify(\n          getState('Counter')\n        )}`\n      )\n  }, [])\n  return (\n    <>\n      <div>state: {JSON.stringify(state)}</div>\n    </>\n  )\n}\n```\n\n[⇧ back to top](#table-of-contents)\n\n### actions\n\nYou can call other models' actions with getActions api\n\ngetActions 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: () => {\n      actions.Shared.changeTheme('dark')\n      actions.Counter.increment(9)\n    }\n  }\n}\n\nexport default model\n```\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 = () => {\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (s, _, params) => {\n      // issue: https://github.com/Microsoft/TypeScript/issues/29196\n      // async function return produce need define type manually.\n      return (state: typeof s) => {\n        state.counter += params || 1\n      }\n    },\n    decrease: (s, _, params) => s => {\n      s.counter += params || 1\n    }\n  }\n}\n```\n\nJavaScript Example\n\n```js\nconst Model = {\n  actions: {\n    increment: async (s, _, params) => {\n      return state => {\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<details>\n<summary>Store: shared.ts</summary>\n<p>\n\n```ts\nconst initialState = {\n  counter: 0\n}\n\nconst Model: ModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: (state, _, params) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    }\n  },\n  // Provide for SSR\n  asyncState: async context => {\n    await waitFor(4000)\n    return { counter: 500 }\n  },\n  state: initialState\n}\n```\n\n</p>\n</details>\n\n<details>\n<summary>Global Config: _app.tsx</summary>\n<p>\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) => {\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    <Container>\n      <Component {...pageProps} />\n    </Container>\n  )\n}\n\nMyApp.getInitialProps = async (context: NextAppContext) => {\n  if (!(process as any).browser) {\n    const initialModels = context.Component.getInitialProps\n      ? await context.Component.getInitialProps(context.ctx)\n      await getInitialState() // get all model initialState\n      // : await getInitialState({ modelName: 'Home' }) // get Home initialState only\n      // : await getInitialState({ modelName: ['Home', 'Todo'] }) // get multi initialState\n      // : await getInitialState({ data }) // You can also pass some public data as asyncData params.\n    return { initialModels }\n  } else {\n    return { persistModel }\n  }\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Page: hooks/index.tsx</summary>\n<p>\n\n```tsx\nimport { useStore, getState } from '../index'\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button\n        onClick={e => {\n          actions.increment(33)\n        }}\n      >\n    </div>\n  )\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Single Page Config: benchmark.tsx</summary>\n<p>\n\n```tsx\n// ...\nBenchmark.getInitialProps = async () => {\n  return await getInitialState({ modelName: 'Todo' })\n}\n```\n</p>\n</details>\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<{}> = async (context, restMiddlewares) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: any) => 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) => async (params: any) => {\n  const context: Context = {\n    modelName,\n    setState,\n    actionName: action.name,\n    next: () => {},\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## 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      <Provider>\n        <Counter />\n      </Provider>\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<details>\n<summary>Javascript decorator version</summary>\n<p>\n\n```jsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\n\nconst mapProps = ({ light, counter }) => ({\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      <>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(5)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n      </>\n    )\n  }\n}\n```\n\n</p>\n</details>\n\n<details>\n<summary>TypeScript Version</summary>\n<p>\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) => ({\n  lightStatus: light ? 'open' : 'close',\n  counter,\n  response\n})\n\ntype RType = ReturnType<typeof mapProps>\n\nclass TSCounter extends PureComponent<\n  { state: RType } & { actions: ActionType }\n> {\n  render() {\n    const { state, actions } = this.props\n    return (\n      <>\n        <div>TS Counter</div>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(3)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n        <button onClick={e => actions.get()}>Get Response</button>\n        <div>message: {JSON.stringify(state.response)}</div>\n      </>\n    )\n  }\n}\n\nexport default connect(\n  'Home',\n  mapProps\n)(TSCounter)\n```\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\n\n## FAQ\n\n### How can I disable the console debugger\n\nJust remove consoleDebugger middleware.\n\n```typescript\nimport { actionMiddlewares } from 'react-model'\n// Find the index of middleware\nconst consoleDebuggerMiddlewareIndex = actionMiddlewares.indexOf(\n  middlewares.consoleDebugger\n)\n// Remove it\nactionMiddlewares.splice(consoleDebuggerMiddlewareIndex, 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) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: Error) => 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 } from 'react-model'\n\nconst persistMiddleware: Middleware = async (context, restMiddlewares) => {\n  localStorage.setItem('__REACT_MODEL__', context.Global.State)\n  await context.next(restMiddlewares)\n}\n\nactionMiddlewares.push(persistMiddleware)\n```\n\n[⇧ back to top](#table-of-contents)\n","readmeFilename":"README.md","_id":"react-model@2.7.0-alpha.0","_nodeVersion":"10.15.0","_npmVersion":"6.9.0","dist":{"integrity":"sha512-Z3Np2ItjAZHFCt543p9OJj8ZOaI9MGAoL45Vl3DqbnG7I98KNnQs2mZe0YNhMIqy7VcCnGKMKqM5dLM2knw09A==","shasum":"ada84b1ba5f1e8102ca14333b55214df64616b3f","tarball":"https://registry.npmjs.org/react-model/-/react-model-2.7.0-alpha.0.tgz","fileCount":21,"unpackedSize":69729,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJcpLddCRA9TVsSAnZWagAAlisP/1zq3qZJWhTubE6unJks\naLaWrlWjAZWjhXAAByjWiA338rUK9w7mmHFO5rHe2Ut+sM82h6aJ0nIH4vXz\nRi8m5340HKv1isvWk5mMSOCHhV5rl9kDZfLfDRT4tkkVJXvh8H7um2ryuaea\n8PkJMXFO5gsOCtjxB0EcGk/ko33z+f9dBl1Ti8VZLd+kD44AKC+hSe02JwDe\ncTDoAPbTXzLYPmcOo76WW5UedIEWjw1hvNPgDJ/Cp/IL7wcn3Hx93N9HtxA8\n1/nFrZCsFjYSAYgO5BwqqtSFRKE3f47hlxCIupvneMmYvtRpbBqWKNeMqMx8\ncdodTyQt3tWVIU+4s/RZJveiEtk+KDA1p6QAYH5kQJYJ/XhTbK6TF5ZSjODf\nnTRcaWnh2IZK9742vxc0QxERCQcCfF8oYZFOIWNcmVZfXwNPQUn32ciXB6A7\nbXsY5FaAtpxV0h0Z8WbVz6w14kHgs5/z4S7DS8PbuIEK/JN94JySgU4xHh9H\naSrP1gKJAHjjCepswPLiv9uz6cbLzn3bJRoUrjKpIZZPtmXOCLLSjrM8I3CS\n1dO0bH5ydEYw3wgCs6qLzdSY9yU/rBF7jDp9F1Pbc3WxhQXNfEBqCaEsf8lY\nnrXTgbg6MaI1GDD30cE7XSGmV705kXMk6FIoNMdNwLKa0hvOcGskjGqsOOXI\nKFzq\r\n=KxT8\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIHgavy2F5ZQAkyOatm95yURg3yrBAnL3pR0JgZp8Fsy1AiA1n2j2sVghzbdZWOg9T5bQqS4UsaycxeU8oTFxbXl+kg=="}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_2.7.0-alpha.0_1554298716261_0.07422213486380147"},"_hasShrinkwrap":false},"2.7.0-alpha.1":{"name":"react-model","version":"2.7.0-alpha.1","description":"The State management library for React","main":"./dist/react-model.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^2.0.0"},"peerDependencies":{"react":"^16.3.0","react-dom":"^16.3.0"},"devDependencies":{"@commitlint/cli":"^7.5.2","@commitlint/config-conventional":"^7.5.0","@types/jest":"^24.0.5","@types/node":"^11.9.4","@types/react":"^16.8.2","@types/react-dom":"^16.8.0","commitizen":"^3.0.7","cz-conventional-changelog":"^2.1.0","husky":"^1.3.1","jest":"^24.1.0","prettier":"^1.16.4","react":"^16.8.4","react-dom":"^16.8.4","react-hooks-testing-library":"^0.4.0","react-testing-library":"^6.0.0","remark-cli":"^6.0.1","remark-lint":"^6.0.4","remark-preset-lint-recommended":"^3.0.2","ts-jest":"^24.0.0","tslint":"^5.14.0","typescript":"^3.3.4000"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"gitHead":"12fce1222b9d73dadca0e9ad49d3e9dac0571c95","readme":"# react-model &middot; ![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) [![Build Status](https://travis-ci.org/byte-fe/react-model.svg?branch=master)](https://travis-ci.org/byte-fe/react-model) [![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⚔ Fully TypeScript Support\n\n📦 built with microbundle\n\n⚙️ Middlewares Pipline ( redux-devtools support ... )\n\n☂️ 100% test coverage, safe on production\n\n🐛 Debug easily on test environment\n\n```tsx\nimport { Model } from 'react-model'\n\n// define model\nconst Todo = {\n  state: {\n    items: ['Install react-model', 'Read github docs', 'Build App']\n  },\n  actions: {\n    add: todo => {\n      // s is the readonly version of state\n      // you can also return partial state here but don't need to keep immutable manually\n      // state is the mutable state\n      return state => state.items.push(todo)\n    }\n  }\n}\n\n// Model Register\nconst { useStore } = Model(Todo)\n\nconst App = () => {\n  return <TodoList />\n}\n\nconst TodoList = () => {\n  const [state, actions] = useStore()\n  return <div>\n    <Addon handler={actions.add} />\n    {state.items.map((item, index) => (<Todo key={index} item={item} />))}\n  </div>\n}\n```\n\n---\n\n## Quick Start\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\ninstall package\n\n```shell\nnpm install react-model\n```\n\n## Table of Contents\n\n- [Core Concept](#core-concept)\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- [Other Concept required by Class Component](#other-concept-required-by-class-component)\n  - [Provider](#provider)\n  - [connect](#connect)\n- [FAQ](#faq)\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\n## Core Concept\n\n### Model\n\nEvery model have their 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: NextModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (payload, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    },\n    openLight: async (_, {state, actions}) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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(model)\n\n// You can use these types when use Class Components.\n// type ConsumerActionsType = getConsumerActionsType<typeof Model.actions>\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 keep the state and actions in a separate store. So you need to register them before using.\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 stores = { Home, Shared }\n\nexport const { getInitialState, useStore, getState, getActions, subscribe, unsubscribe } = Model(stores)\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 return from useStore can invoke the 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 getActions now.\n\n```tsx\nimport React from 'react'\nimport { useStore } from '../index'\n\n// CSR\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button onClick={e => actions.increment(33)}>home increment</button>\n      <button onClick={e => sharedActions.increment(20)}>\n        shared increment\n      </button>\n      <button onClick={e => actions.get()}>fake request</button>\n      <button onClick={e => actions.openLight()}>fake nested call</button>\n    </div>\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 with depActions and React.memo to prevent child components rerender frequently.](https://github.com/ArrayZoneYour/react-model-todomvc/blob/master/src/components/TodoItem.tsx)\n\n[Demo Repo](https://github.com/ArrayZoneYour/react-model-todomvc)\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> Hint: The state returned should only be used as readonly\n\n```jsx\nimport { useStore, getState } from '../model/index'\n\nconst BasicHook = () => {\n  const [state, actions] = useStore('Counter')\n  useEffect(() => {\n    console.log('some mounted actions from BasicHooks')\n    return () =>\n      console.log(\n        `Basic Hooks unmounted, current Counter state: ${JSON.stringify(\n          getState('Counter')\n        )}`\n      )\n  }, [])\n  return (\n    <>\n      <div>state: {JSON.stringify(state)}</div>\n    </>\n  )\n}\n```\n\n[⇧ back to top](#table-of-contents)\n\n### actions\n\nYou can call other models' actions with getActions api\n\ngetActions 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: () => {\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 = () => {\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: NextModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (s, _, params) => {\n      // issue: https://github.com/Microsoft/TypeScript/issues/29196\n      // async function return produce need define type manually.\n      return (state: typeof s) => {\n        state.counter += params || 1\n      }\n    },\n    decrease: (s, _, params) => s => {\n      s.counter += params || 1\n    }\n  }\n}\n\nexport default Model(model)\n```\n\nJavaScript Example\n\n```js\nconst Model = {\n  actions: {\n    increment: async (s, _, params) => {\n      return state => {\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<details>\n<summary>Store: shared.ts</summary>\n<p>\n\n```ts\nconst initialState = {\n  counter: 0\n}\n\nconst model: NextModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: (params, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    }\n  },\n  // Provide for SSR\n  asyncState: async context => {\n    await waitFor(4000)\n    return { counter: 500 }\n  },\n  state: initialState\n}\n\nexport default Model(model)\n```\n\n</p>\n</details>\n\n<details>\n<summary>Global Config: _app.tsx</summary>\n<p>\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) => {\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    <Container>\n      <Component {...pageProps} />\n    </Container>\n  )\n}\n\nMyApp.getInitialProps = async (context: NextAppContext) => {\n  if (!(process as any).browser) {\n    const initialModels = context.Component.getInitialProps\n      ? await context.Component.getInitialProps(context.ctx)\n      await getInitialState() // get all model initialState\n      // : await getInitialState({ modelName: 'Home' }) // get Home initialState only\n      // : await getInitialState({ modelName: ['Home', 'Todo'] }) // get multi initialState\n      // : await getInitialState({ data }) // You can also pass some public data as asyncData params.\n    return { initialModels }\n  } else {\n    return { persistModel }\n  }\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Page: hooks/index.tsx</summary>\n<p>\n\n```tsx\nimport { useStore, getState } from '../index'\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button\n        onClick={e => {\n          actions.increment(33)\n        }}\n      >\n    </div>\n  )\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Single Page Config: benchmark.tsx</summary>\n<p>\n\n```tsx\n// ...\nBenchmark.getInitialProps = async () => {\n  return await getInitialState({ modelName: 'Todo' })\n}\n```\n</p>\n</details>\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<{}> = async (context, restMiddlewares) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: any) => 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) => async (params: any) => {\n  const context: Context = {\n    modelName,\n    setState,\n    actionName: action.name,\n    next: () => {},\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## 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      <Provider>\n        <Counter />\n      </Provider>\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<details>\n<summary>Javascript decorator version</summary>\n<p>\n\n```jsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\n\nconst mapProps = ({ light, counter }) => ({\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      <>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(5)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n      </>\n    )\n  }\n}\n```\n\n</p>\n</details>\n\n<details>\n<summary>TypeScript Version</summary>\n<p>\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) => ({\n  lightStatus: light ? 'open' : 'close',\n  counter,\n  response\n})\n\ntype RType = ReturnType<typeof mapProps>\n\nclass TSCounter extends PureComponent<\n  { state: RType } & { actions: ActionType }\n> {\n  render() {\n    const { state, actions } = this.props\n    return (\n      <>\n        <div>TS Counter</div>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(3)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n        <button onClick={e => actions.get()}>Get Response</button>\n        <div>message: {JSON.stringify(state.response)}</div>\n      </>\n    )\n  }\n}\n\nexport default connect(\n  'Home',\n  mapProps\n)(TSCounter)\n```\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\n\n## FAQ\n\n### How can I disable the console debugger\n\nJust remove consoleDebugger middleware.\n\n```typescript\nimport { actionMiddlewares } from 'react-model'\n// Find the index of middleware\nconst consoleDebuggerMiddlewareIndex = actionMiddlewares.indexOf(\n  middlewares.consoleDebugger\n)\n// Remove it\nactionMiddlewares.splice(consoleDebuggerMiddlewareIndex, 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) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: Error) => 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\nconst persistMiddleware: Middleware = async (context, restMiddlewares) => {\n  localStorage.setItem('__REACT_MODEL__', JSON.stringify(context.Global.State))\n  await context.next(restMiddlewares)\n}\n\nactionMiddlewares.push(persistMiddleware)\n\nModel({ Example }, JSON.parse(localStorage.getItem('__REACT_MODEL__')))\n```\n\n[⇧ back to top](#table-of-contents)\n","readmeFilename":"README.md","_id":"react-model@2.7.0-alpha.1","_nodeVersion":"10.15.0","_npmVersion":"6.9.0","dist":{"integrity":"sha512-UrwdL+M/iA5iRkV0A8syyC/i33GTG31YmWnIqhrDq8dJ34hFaMFwijU+oO9wxqHRDea12h9yrYaAiIb5vui9gQ==","shasum":"7230e199ad934005a7e970a6be96b71f8079359e","tarball":"https://registry.npmjs.org/react-model/-/react-model-2.7.0-alpha.1.tgz","fileCount":21,"unpackedSize":70036,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJcqrbVCRA9TVsSAnZWagAAz9IP/2BPcb55LQygwSZo9MEv\nX/7ew4C2Dc/thXEvgEDu8rVGj1fezyWGW1qEYOGVvMV4DevOpyovD9bNXsyd\nbMv9AfRZEuvL2P+D1JJ67pew7086q2npqgnDMJr/fOhvLkb5OiZ+YxXhv7SL\nKwl3MIcAjcUNhcS3eC5O/hNZv9vUG1BjSElgvqrDp6AyqhC2KV6xbDVticJo\nEiIOcmQEDj3hFEMxACcYUOY5MOKdMzW2JicPqVP0XeO2uI0LdcfggqsMgfpB\nYdUXzfP/TEUn6PWyJRWl/i0+4eW0ptXyqq2ANNic65ZIvYXQiK2UKj8PCuHc\np1BuJOIBx8Y7vgWUJLuPtILLfaseyR1uyQqhAd9ByFwXyYFwdrrHHuVba8cD\n5NC5H8/mTU4nuZc1ddVjyi31KTu/nhfHbVYP/o3VoVNY6NtpwrSe3I9VZVoN\n3mwOPH7z9b4xeITNuwWIAR2MZAkGcB4n1XUvfjjNTixGWx8r6+hXNApzYdMu\nB8+2zeFVeMXyQ8My/sGOMOoPG5X5J7N8/mhvnE2jnPDDFu2o4mJ6YPvHsWBj\nXEP1tuX1yrDpBKeB39izNnd6zeJ5PbQGXRhsby8Tk5fQ1v+1/i42hzJiqM+5\neRCsGB8FKEzii42w5gOeB+GVUOMJVjnFBn8B0JJULs2q1eGjABs1MUwqY4Si\nI3xn\r\n=qWtf\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIHwwkLIayH0SLgVEkF94Rr+HGgK7H8zXbbMdMLZXP6L7AiALnZbZ/1GLRAGS6GotGRLW6PNN1BQsEuNpGNkpJkq39A=="}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_2.7.0-alpha.1_1554691796342_0.5880442460917263"},"_hasShrinkwrap":false},"2.7.0":{"name":"react-model","version":"2.7.0","description":"The State management library for React","main":"./dist/react-model.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^2.0.0"},"peerDependencies":{"react":"^16.3.0","react-dom":"^16.3.0"},"devDependencies":{"@commitlint/cli":"^7.5.2","@commitlint/config-conventional":"^7.5.0","@types/jest":"^24.0.5","@types/node":"^11.9.4","@types/react":"^16.8.2","@types/react-dom":"^16.8.0","commitizen":"^3.0.7","cz-conventional-changelog":"^2.1.0","husky":"^1.3.1","jest":"^24.1.0","prettier":"^1.16.4","react":"^16.8.4","react-dom":"^16.8.4","react-hooks-testing-library":"^0.4.0","react-testing-library":"^6.0.0","remark-cli":"^6.0.1","remark-lint":"^6.0.4","remark-preset-lint-recommended":"^3.0.2","ts-jest":"^24.0.0","tslint":"^5.14.0","typescript":"^3.3.4000"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"gitHead":"1913871ace75d6f2509e089a321fb067c139ce43","_id":"react-model@2.7.0","_nodeVersion":"11.11.0","_npmVersion":"6.9.0","dist":{"integrity":"sha512-G261FSlcv5TpCyEL+O8OZT6AwaM+PB0vaG4YPBehT6h0dlISv+tyNw76edFZg3eeiMPqhGWYlfyXnEZ2o1Zdqw==","shasum":"db5a2314dca599da63b8fcd2492dd77c211a78f7","tarball":"https://registry.npmjs.org/react-model/-/react-model-2.7.0.tgz","fileCount":21,"unpackedSize":73619,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJcsEvZCRA9TVsSAnZWagAAxC8P/jJ1y6LIe8wZAAsO5sOU\npvP51KnUjYjJIPnjMfHMB6/Yr7gS4xk/UOUrJwAuxkBwiJOZjJV2RQWXobIt\nSNB6YtBnJ736RGM6B1Zro5IktHMx5yi2xgpXc7hf80ZwlOEwN+V09NcKIvTO\n4xz/Dy3XAE/9Zddu+CiIfaH0T4EocYHsQKzftudzGQJU1pH9MaWfRBh+gK4e\ny6PLScvK2E39MRJhXSFkFrfjgE05PjtMgr9FkCUMTnFHQ9fy9bQv7qyj/Rau\neMBJPqRMET3b55o9Ifli70Fk3a1KnCANfMOMf3c4yfOuGcoLpdFynPEFXUqR\ndNUflyPVy4Bav22XrTclxhqYB+8yfYt0U82J1kIOxTqbzd5WMUBQLHRpr84q\nkp0Ir7NNKbzP/TMIt+WCwXyxUoXs4ofRdrWiKR4wbs/OrO4OF1n99WvFF6je\n4JD9ro7fwM5g2UoJDsz4X9CyuAeE3omyNgYgecTU6JEbcO9M5PhSymsOX+9C\nDxC/HnSecaex8jtc4XDhLARPedJJCvBATUHayFZdW9EQyMkm6cPw7a1q0ttL\n3QBgcgDfl4Xoi3miHaB6hOdnIhaR+dy+mM61HHeBshZA75hOArVKI/jEQOvM\n5W8Fc5P7sO2iNpK1l/O7KtK7/1Z1CwMMG7dGyRm+A8UhwpH9/YmGk5QhdChb\nKmFT\r\n=EkoC\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIEcoQZUCin5h4oNV8By10+K0SyTy79X+nDlfSoG8PcqfAiEA5mBMzEgs/K2xaCdsvMpYD2ngOG8b/ly5dU9pu3ipmJw="}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_2.7.0_1555057624279_0.3011160441623524"},"_hasShrinkwrap":false},"2.7.1-alpha.0":{"name":"react-model","version":"2.7.1-alpha.0","description":"The State management library for React","main":"./dist/react-model.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^2.0.0","ts-transformer-keys":"^0.3.4"},"peerDependencies":{"react":"^16.3.0","react-dom":"^16.3.0"},"devDependencies":{"@commitlint/cli":"^7.5.2","@commitlint/config-conventional":"^7.5.0","@types/jest":"^24.0.5","@types/node":"^11.9.4","@types/react":"^16.8.2","@types/react-dom":"^16.8.0","commitizen":"^3.0.7","cz-conventional-changelog":"^2.1.0","husky":"^1.3.1","jest":"^24.1.0","prettier":"^1.16.4","react":"^16.8.4","react-dom":"^16.8.4","react-hooks-testing-library":"^0.4.0","react-testing-library":"^6.0.0","remark-cli":"^6.0.1","remark-lint":"^6.0.4","remark-preset-lint-recommended":"^3.0.2","ts-jest":"^24.0.0","tslint":"^5.14.0","typescript":"^3.3.4000"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"gitHead":"1913871ace75d6f2509e089a321fb067c139ce43","readme":"# react-model &middot; ![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) [![Build Status](https://travis-ci.org/byte-fe/react-model.svg?branch=master)](https://travis-ci.org/byte-fe/react-model) [![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⚔ Fully TypeScript Support\n\n📦 built with microbundle\n\n⚙️ Middlewares Pipline ( redux-devtools support ... )\n\n☂️ 100% test coverage, safe on production\n\n🐛 Debug easily on test environment\n\n```tsx\nimport { Model } from 'react-model'\n\n// define model\nconst Todo = {\n  state: {\n    items: ['Install react-model', 'Read github docs', 'Build App']\n  },\n  actions: {\n    add: todo => {\n      // s is the readonly version of state\n      // you can also return partial state here but don't need to keep immutable manually\n      // state is the mutable state\n      return state => state.items.push(todo)\n    }\n  }\n}\n\n// Model Register\nconst { useStore } = Model(Todo)\n\nconst App = () => {\n  return <TodoList />\n}\n\nconst TodoList = () => {\n  const [state, actions] = useStore()\n  return <div>\n    <Addon handler={actions.add} />\n    {state.items.map((item, index) => (<Todo key={index} item={item} />))}\n  </div>\n}\n```\n\n---\n\n## Quick Start\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\ninstall package\n\n```shell\nnpm install react-model\n```\n\n## Table of Contents\n\n- [Core Concept](#core-concept)\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- [Other Concept required by Class Component](#other-concept-required-by-class-component)\n  - [Provider](#provider)\n  - [connect](#connect)\n- [FAQ](#faq)\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  - [actions throw error from immer.module.js](#actions-throw-error-from-immer.module.js)\n\n## Core Concept\n\n### Model\n\nEvery model have their own state and actions.\n\n<details>\n<summary><del>old model</del> (will be deprecated in v3.0)</summary>\n<p>\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (state, _, params) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    },\n    openLight: async (state, actions) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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<typeof Model.actions>\n// type ConsumerType = { actions: ConsumerActionsType; state: StateType }\n// type ActionType = ConsumerActionsType\n// export { ConsumerType, StateType, ActionType }\n```\n</p>\n</details>\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: NextModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (payload, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    },\n    openLight: async (_, { state, actions }) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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(model)\n\n// You can use these types when use Class Components.\n// type ConsumerActionsType = getConsumerActionsType<typeof Model.actions>\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 keep the state and actions in the separate private store. 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 return from useStore can invoke the 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 () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button onClick={e => actions.increment(33)}>home increment</button>\n      <button onClick={e => sharedActions.increment(20)}>\n        shared increment\n      </button>\n      <button onClick={e => actions.get()}>fake request</button>\n      <button onClick={e => actions.openLight()}>fake nested call</button>\n    </div>\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 with depActions and React.memo to prevent child components rerender frequently.](https://github.com/ArrayZoneYour/react-model-todomvc/blob/master/src/components/TodoItem.tsx)\n\n[Demo Repo](https://github.com/ArrayZoneYour/react-model-todomvc)\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> Hint: The state returned should only be used as readonly\n\n```jsx\nimport { useStore, getState } from '../model/index'\n\nconst BasicHook = () => {\n  const [state, actions] = useStore('Counter')\n  useEffect(() => {\n    console.log('some mounted actions from BasicHooks')\n    return () =>\n      console.log(\n        `Basic Hooks unmounted, current Counter state: ${JSON.stringify(\n          getState('Counter')\n        )}`\n      )\n  }, [])\n  return (\n    <>\n      <div>state: {JSON.stringify(state)}</div>\n    </>\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: () => {\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 = () => {\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: NextModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (s, _, params) => {\n      // issue: https://github.com/Microsoft/TypeScript/issues/29196\n      // async function return produce need define type manually.\n      return (state: typeof s) => {\n        state.counter += params || 1\n      }\n    },\n    decrease: (s, _, params) => s => {\n      s.counter += params || 1\n    }\n  }\n}\n\nexport default Model(model)\n```\n\nJavaScript Example\n\n```js\nconst Model = {\n  actions: {\n    increment: async (s, _, params) => {\n      return state => {\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<details>\n<summary>Store: shared.ts</summary>\n<p>\n\n```ts\nconst initialState = {\n  counter: 0\n}\n\nconst model: NextModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: (params, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    }\n  },\n  // Provide for SSR\n  asyncState: async context => {\n    await waitFor(4000)\n    return { counter: 500 }\n  },\n  state: initialState\n}\n\nexport default Model(model)\n```\n\n</p>\n</details>\n\n<details>\n<summary>Global Config: _app.tsx</summary>\n<p>\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) => {\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    <Container>\n      <Component {...pageProps} />\n    </Container>\n  )\n}\n\nMyApp.getInitialProps = async (context: NextAppContext) => {\n  if (!(process as any).browser) {\n    const initialModels = context.Component.getInitialProps\n      ? await context.Component.getInitialProps(context.ctx)\n      await getInitialState() // get all model initialState\n      // : await getInitialState({ modelName: 'Home' }) // get Home initialState only\n      // : await getInitialState({ modelName: ['Home', 'Todo'] }) // get multi initialState\n      // : await getInitialState({ data }) // You can also pass some public data as asyncData params.\n    return { initialModels }\n  } else {\n    return { persistModel }\n  }\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Page: hooks/index.tsx</summary>\n<p>\n\n```tsx\nimport { useStore, getState } from '../index'\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button\n        onClick={e => {\n          actions.increment(33)\n        }}\n      >\n    </div>\n  )\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Single Page Config: benchmark.tsx</summary>\n<p>\n\n```tsx\n// ...\nBenchmark.getInitialProps = async () => {\n  return await getInitialState({ modelName: 'Todo' })\n}\n```\n</p>\n</details>\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<{}> = async (context, restMiddlewares) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: any) => 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) => async (params: any) => {\n  const context: Context = {\n    modelName,\n    setState,\n    actionName: action.name,\n    next: () => {},\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## 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      <Provider>\n        <Counter />\n      </Provider>\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<details>\n<summary>Javascript decorator version</summary>\n<p>\n\n```jsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\n\nconst mapProps = ({ light, counter }) => ({\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      <>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(5)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n      </>\n    )\n  }\n}\n```\n\n</p>\n</details>\n\n<details>\n<summary>TypeScript Version</summary>\n<p>\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) => ({\n  lightStatus: light ? 'open' : 'close',\n  counter,\n  response\n})\n\ntype RType = ReturnType<typeof mapProps>\n\nclass TSCounter extends PureComponent<\n  { state: RType } & { actions: ActionType }\n> {\n  render() {\n    const { state, actions } = this.props\n    return (\n      <>\n        <div>TS Counter</div>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(3)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n        <button onClick={e => actions.get()}>Get Response</button>\n        <div>message: {JSON.stringify(state.response)}</div>\n      </>\n    )\n  }\n}\n\nexport default connect(\n  'Home',\n  mapProps\n)(TSCounter)\n```\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\n\n## FAQ\n\n### How can I disable the console debugger\n\nJust remove consoleDebugger middleware.\n\n```typescript\nimport { actionMiddlewares } from 'react-model'\n// Find the index of middleware\nconst consoleDebuggerMiddlewareIndex = actionMiddlewares.indexOf(\n  middlewares.consoleDebugger\n)\n// Remove it\nactionMiddlewares.splice(consoleDebuggerMiddlewareIndex, 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) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: Error) => 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\nconst persistMiddleware: Middleware = async (context, restMiddlewares) => {\n  localStorage.setItem('__REACT_MODEL__', JSON.stringify(context.Global.State))\n  await context.next(restMiddlewares)\n}\n\nactionMiddlewares.push(persistMiddleware)\n\nModel({ Example }, JSON.parse(localStorage.getItem('__REACT_MODEL__')))\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      <div className=\"App\">\n        <Counter />\n        <Counter />\n        <Counter />\n      </div>\n    )\n  }\n}\n```\n\n<details>\n<summary>Counter model</summary>\n<p>\n\n```ts\ninterface State {\n  count: number\n}\n\ninterface ActionParams {\n  increment: number\n}\n\nconst model: NextModelType<State, ActionParams> = {\n  state: {\n    count: 0\n  },\n  actions: {\n    increment: payload => {\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 => (state.count += payload)\n      return state => {\n        state.count += payload\n      }\n    }\n  }\n}\n\n```\n\n</p>\n</details>\n\n<details>\n<summary>Counter.tsx</summary>\n<p>\n\n```tsx\n\nconst Counter = () => {\n  const [{ useStore }] = useState(() => Model(model))\n  const [state, actions] = useStore()\n  return (\n    <div>\n      <div>{state.count}</div>\n      <button onClick={() => actions.increment(3)}>Increment</button>\n    </div>\n  )\n}\n\nexport default Counter\n```\n\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\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 => {\n    // Not allowed\n    // return state => (state.count += payload)\n    return state => {\n      state.count += payload\n    }\n  }\n}\n```\n\n[⇧ back to top](#table-of-contents)","readmeFilename":"README.md","_id":"react-model@2.7.1-alpha.0","_nodeVersion":"11.11.0","_npmVersion":"6.9.0","dist":{"integrity":"sha512-C0zVQsUAwRMatwv1Z2rjS8/8Mi/vleooAa6/EsD3qFsFnJ69lKSrFhynBGOnVEzRqRESKX8IBpjjRFpXdrnW2g==","shasum":"3bba1c9f5fcddc0b07e496acf271eab3d3403070","tarball":"https://registry.npmjs.org/react-model/-/react-model-2.7.1-alpha.0.tgz","fileCount":21,"unpackedSize":74260,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJcvaVVCRA9TVsSAnZWagAAClcP/1vWPGa5DhDUZIDISTVo\n2sFY3Rho7gqbP7Rcw4eBAfZDnydNEbb9/m2PS0RpT76uE7niL4hUguRUSBnj\nph+BcLxaJt8ht661SrBPGpSc0NbsVx83UFOsUbBP29uti3VBXw2L3SZWGK+4\nKJybMn2PZ8R7wg+gpq2d7Z4FJcD5t7yoLVbsGJETC+YuEFcOlkpMs0l0GaKB\nzrY9Kq+NAk/TsU5uEgVNPjBsgHIIs5sdB2i+gPD/EyL3Vp6HoaTsX6+zASXX\nnRzcekYT1qYGpEj4nKTz1VCu5XZSMPvKHsWvb5BXPx5YOPhd3dfgxTpMvgGH\nbzB6gPDd0eGPOJ2v5m+5NgirMXFzc0SsuMLKUFqB7zwW1UCmCgvp13voANYQ\nl57vJKpuvn3wuPigIF2RgPr7lGBh5oEdOXR+t93LzMzNiJqlmw4Nt5f7Mukl\ntKuNpvEsbsZkqRG7++yRDiaq1E8axb84UbQtUxR0Ji6Pa9c/XEEgoCK7U1Mx\nDbOAm9LdbuVDIijWZKxOJquIFJuouM39PjI1xT2OJQyy/jRkrxSyZOp114VG\ndo4ru91M9C0bu+UKXwfCmOpFN0KEuQO2h4jR8aQQj4SBiV/QI4Qx3u4Gy64K\nZI3/I2s6nr3HO/Aj2UnwVhesjWPGPEoCTQhDrXMp/yo7KJjfCaSQ/3TdnVZ3\nu2zY\r\n=898B\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDFe9zWkS2qni+PRQmbM1VedyDbEEG/jKW8QPC0UyHnIAIhANeBueImxkBjf3DjHcgYTzN53dECnJCKOdj68utPPkHj"}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_2.7.1-alpha.0_1555932500672_0.6427443605912078"},"_hasShrinkwrap":false},"2.7.1-alpha.1":{"name":"react-model","version":"2.7.1-alpha.1","description":"The State management library for React","main":"./dist/react-model.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^2.0.0"},"peerDependencies":{"react":"^16.3.0","react-dom":"^16.3.0"},"devDependencies":{"@commitlint/cli":"^7.5.2","@commitlint/config-conventional":"^7.5.0","@types/jest":"^24.0.5","@types/node":"^11.9.4","@types/react":"^16.8.2","@types/react-dom":"^16.8.0","commitizen":"^3.0.7","cz-conventional-changelog":"^2.1.0","husky":"^1.3.1","jest":"^24.1.0","prettier":"^1.16.4","react":"^16.8.4","react-dom":"^16.8.4","react-hooks-testing-library":"^0.4.0","react-testing-library":"^6.0.0","remark-cli":"^6.0.1","remark-lint":"^6.0.4","remark-preset-lint-recommended":"^3.0.2","ts-jest":"^24.0.0","tslint":"^5.14.0","typescript":"^3.3.4000"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"gitHead":"1913871ace75d6f2509e089a321fb067c139ce43","readme":"# react-model &middot; ![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) [![Build Status](https://travis-ci.org/byte-fe/react-model.svg?branch=master)](https://travis-ci.org/byte-fe/react-model) [![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⚔ Fully TypeScript Support\n\n📦 built with microbundle\n\n⚙️ Middlewares Pipline ( redux-devtools support ... )\n\n☂️ 100% test coverage, safe on production\n\n🐛 Debug easily on test environment\n\n```tsx\nimport { Model } from 'react-model'\n\n// define model\nconst Todo = {\n  state: {\n    items: ['Install react-model', 'Read github docs', 'Build App']\n  },\n  actions: {\n    add: todo => {\n      // s is the readonly version of state\n      // you can also return partial state here but don't need to keep immutable manually\n      // state is the mutable state\n      return state => state.items.push(todo)\n    }\n  }\n}\n\n// Model Register\nconst { useStore } = Model(Todo)\n\nconst App = () => {\n  return <TodoList />\n}\n\nconst TodoList = () => {\n  const [state, actions] = useStore()\n  return <div>\n    <Addon handler={actions.add} />\n    {state.items.map((item, index) => (<Todo key={index} item={item} />))}\n  </div>\n}\n```\n\n---\n\n## Quick Start\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\ninstall package\n\n```shell\nnpm install react-model\n```\n\n## Table of Contents\n\n- [Core Concept](#core-concept)\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- [Other Concept required by Class Component](#other-concept-required-by-class-component)\n  - [Provider](#provider)\n  - [connect](#connect)\n- [FAQ](#faq)\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  - [actions throw error from immer.module.js](#actions-throw-error-from-immer.module.js)\n\n## Core Concept\n\n### Model\n\nEvery model have their own state and actions.\n\n<details>\n<summary><del>old model</del> (will be deprecated in v3.0)</summary>\n<p>\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (state, _, params) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    },\n    openLight: async (state, actions) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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<typeof Model.actions>\n// type ConsumerType = { actions: ConsumerActionsType; state: StateType }\n// type ActionType = ConsumerActionsType\n// export { ConsumerType, StateType, ActionType }\n```\n</p>\n</details>\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: NextModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (payload, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    },\n    openLight: async (_, { state, actions }) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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(model)\n\n// You can use these types when use Class Components.\n// type ConsumerActionsType = getConsumerActionsType<typeof Model.actions>\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 keep the state and actions in the separate private store. 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 return from useStore can invoke the 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 () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button onClick={e => actions.increment(33)}>home increment</button>\n      <button onClick={e => sharedActions.increment(20)}>\n        shared increment\n      </button>\n      <button onClick={e => actions.get()}>fake request</button>\n      <button onClick={e => actions.openLight()}>fake nested call</button>\n    </div>\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 with depActions and React.memo to prevent child components rerender frequently.](https://github.com/ArrayZoneYour/react-model-todomvc/blob/master/src/components/TodoItem.tsx)\n\n[Demo Repo](https://github.com/ArrayZoneYour/react-model-todomvc)\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> Hint: The state returned should only be used as readonly\n\n```jsx\nimport { useStore, getState } from '../model/index'\n\nconst BasicHook = () => {\n  const [state, actions] = useStore('Counter')\n  useEffect(() => {\n    console.log('some mounted actions from BasicHooks')\n    return () =>\n      console.log(\n        `Basic Hooks unmounted, current Counter state: ${JSON.stringify(\n          getState('Counter')\n        )}`\n      )\n  }, [])\n  return (\n    <>\n      <div>state: {JSON.stringify(state)}</div>\n    </>\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: () => {\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 = () => {\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: NextModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (s, _, params) => {\n      // issue: https://github.com/Microsoft/TypeScript/issues/29196\n      // async function return produce need define type manually.\n      return (state: typeof s) => {\n        state.counter += params || 1\n      }\n    },\n    decrease: (s, _, params) => s => {\n      s.counter += params || 1\n    }\n  }\n}\n\nexport default Model(model)\n```\n\nJavaScript Example\n\n```js\nconst Model = {\n  actions: {\n    increment: async (s, _, params) => {\n      return state => {\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<details>\n<summary>Store: shared.ts</summary>\n<p>\n\n```ts\nconst initialState = {\n  counter: 0\n}\n\nconst model: NextModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: (params, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    }\n  },\n  // Provide for SSR\n  asyncState: async context => {\n    await waitFor(4000)\n    return { counter: 500 }\n  },\n  state: initialState\n}\n\nexport default Model(model)\n```\n\n</p>\n</details>\n\n<details>\n<summary>Global Config: _app.tsx</summary>\n<p>\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) => {\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    <Container>\n      <Component {...pageProps} />\n    </Container>\n  )\n}\n\nMyApp.getInitialProps = async (context: NextAppContext) => {\n  if (!(process as any).browser) {\n    const initialModels = context.Component.getInitialProps\n      ? await context.Component.getInitialProps(context.ctx)\n      await getInitialState() // get all model initialState\n      // : await getInitialState({ modelName: 'Home' }) // get Home initialState only\n      // : await getInitialState({ modelName: ['Home', 'Todo'] }) // get multi initialState\n      // : await getInitialState({ data }) // You can also pass some public data as asyncData params.\n    return { initialModels }\n  } else {\n    return { persistModel }\n  }\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Page: hooks/index.tsx</summary>\n<p>\n\n```tsx\nimport { useStore, getState } from '../index'\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button\n        onClick={e => {\n          actions.increment(33)\n        }}\n      >\n    </div>\n  )\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Single Page Config: benchmark.tsx</summary>\n<p>\n\n```tsx\n// ...\nBenchmark.getInitialProps = async () => {\n  return await getInitialState({ modelName: 'Todo' })\n}\n```\n</p>\n</details>\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<{}> = async (context, restMiddlewares) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: any) => 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) => async (params: any) => {\n  const context: Context = {\n    modelName,\n    setState,\n    actionName: action.name,\n    next: () => {},\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## 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      <Provider>\n        <Counter />\n      </Provider>\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<details>\n<summary>Javascript decorator version</summary>\n<p>\n\n```jsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\n\nconst mapProps = ({ light, counter }) => ({\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      <>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(5)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n      </>\n    )\n  }\n}\n```\n\n</p>\n</details>\n\n<details>\n<summary>TypeScript Version</summary>\n<p>\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) => ({\n  lightStatus: light ? 'open' : 'close',\n  counter,\n  response\n})\n\ntype RType = ReturnType<typeof mapProps>\n\nclass TSCounter extends PureComponent<\n  { state: RType } & { actions: ActionType }\n> {\n  render() {\n    const { state, actions } = this.props\n    return (\n      <>\n        <div>TS Counter</div>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(3)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n        <button onClick={e => actions.get()}>Get Response</button>\n        <div>message: {JSON.stringify(state.response)}</div>\n      </>\n    )\n  }\n}\n\nexport default connect(\n  'Home',\n  mapProps\n)(TSCounter)\n```\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\n\n## FAQ\n\n### How can I disable the console debugger\n\nJust remove consoleDebugger middleware.\n\n```typescript\nimport { actionMiddlewares } from 'react-model'\n// Find the index of middleware\nconst consoleDebuggerMiddlewareIndex = actionMiddlewares.indexOf(\n  middlewares.consoleDebugger\n)\n// Remove it\nactionMiddlewares.splice(consoleDebuggerMiddlewareIndex, 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) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: Error) => 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\nconst persistMiddleware: Middleware = async (context, restMiddlewares) => {\n  localStorage.setItem('__REACT_MODEL__', JSON.stringify(context.Global.State))\n  await context.next(restMiddlewares)\n}\n\nactionMiddlewares.push(persistMiddleware)\n\nModel({ Example }, JSON.parse(localStorage.getItem('__REACT_MODEL__')))\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      <div className=\"App\">\n        <Counter />\n        <Counter />\n        <Counter />\n      </div>\n    )\n  }\n}\n```\n\n<details>\n<summary>Counter model</summary>\n<p>\n\n```ts\ninterface State {\n  count: number\n}\n\ninterface ActionParams {\n  increment: number\n}\n\nconst model: NextModelType<State, ActionParams> = {\n  state: {\n    count: 0\n  },\n  actions: {\n    increment: payload => {\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 => (state.count += payload)\n      return state => {\n        state.count += payload\n      }\n    }\n  }\n}\n\n```\n\n</p>\n</details>\n\n<details>\n<summary>Counter.tsx</summary>\n<p>\n\n```tsx\n\nconst Counter = () => {\n  const [{ useStore }] = useState(() => Model(model))\n  const [state, actions] = useStore()\n  return (\n    <div>\n      <div>{state.count}</div>\n      <button onClick={() => actions.increment(3)}>Increment</button>\n    </div>\n  )\n}\n\nexport default Counter\n```\n\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\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 => {\n    // Not allowed\n    // return state => (state.count += payload)\n    return state => {\n      state.count += payload\n    }\n  }\n}\n```\n\n[⇧ back to top](#table-of-contents)","readmeFilename":"README.md","_id":"react-model@2.7.1-alpha.1","_nodeVersion":"11.11.0","_npmVersion":"6.9.0","dist":{"integrity":"sha512-UEWqt6Su31rdsVI9FVcuTSwYNCf2Rjw812Rn4Kc65EB3qi4VB8B53JqgLcQaIJ7Tj9+MqouIO1BinoV8/HIAZg==","shasum":"53dd985efaf5be4430fc582f30126eb80a08ddc0","tarball":"https://registry.npmjs.org/react-model/-/react-model-2.7.1-alpha.1.tgz","fileCount":21,"unpackedSize":75034,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJcvayqCRA9TVsSAnZWagAAx5AP/2HfZBdXpDZbA0VUkWTa\nxwoXm4CUObnY7CN68UVzLqR3z4cMdiQbgd1Ztt8FcElQUxuDNaHntmMv0e6/\n0Ijme1rM5FuswAAy1mkgarBqVTweYZ6KpkMtPjBAskNs2RajhpY1bS9i3qAT\nInEMf1Mq0CW+OQRxQEDxHaSAjo2pvrEfnPESzIJCWcJ7juRVRceNRM2wJJ45\n4BkfpDIFr7ZIepvXYHIVRQxX/f+KLJujXBTVEOGdxmb+o6uyAar5kyBnjnR2\nDK9pE6N1F3ZwXNjNKd3QhCoTO0ughYqcZfWAmnLd9fVDx1FzPXNEU//2rOD9\nzwpMrWccjM+uQSCGk0939uUV0dJheZEzl6kjJ8V6Zl0uGFUobS7o6ETpbW2Y\nv5yfFoi8bhtHvl336L27OgLJlOeOpWVDtL76El7k28abatIgVNM+XPjhQkJY\ntrpM2mtVhXHdaMWx0zSqL3uAFmbaPubHOLN1GFZJNMpyRA4qWdSucBkFuBKG\nyq52WQ7ApKntPzRQgPW+yv7f4f0EYpAugd2wdE2SRieBiv1YJuSj6PK+8lzj\ntKXQ7DOQEFo2V07VGLH6zmx0H/7hqJoNRavjNBQyrva1JVrvIj1Og1WztLr8\nuIK7HXovvTCvCF5qCYLEN/WznD54Pj0QVQF32/2rG4RCiwaf+BXbOxehkADk\nyUGr\r\n=LNnc\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDRwyNs0I08RXFA42jf2uRK+qj8202SzmKGmT4Jv73Z2wIhAJv5Go95gsI4LVpByPr7+PYrph/W1NGEO7DjRT0ThkM9"}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_2.7.1-alpha.1_1555934377592_0.7929898925542171"},"_hasShrinkwrap":false},"2.7.1-alpha.2":{"name":"react-model","version":"2.7.1-alpha.2","description":"The State management library for React","main":"./dist/react-model.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^2.0.0"},"peerDependencies":{"react":"^16.3.0","react-dom":"^16.3.0"},"devDependencies":{"@commitlint/cli":"^7.5.2","@commitlint/config-conventional":"^7.5.0","@types/jest":"^24.0.5","@types/node":"^11.9.4","@types/react":"^16.8.2","@types/react-dom":"^16.8.0","commitizen":"^3.0.7","cz-conventional-changelog":"^2.1.0","husky":"^1.3.1","jest":"^24.1.0","prettier":"^1.16.4","react":"^16.8.4","react-dom":"^16.8.4","react-hooks-testing-library":"^0.4.0","react-testing-library":"^6.0.0","remark-cli":"^6.0.1","remark-lint":"^6.0.4","remark-preset-lint-recommended":"^3.0.2","ts-jest":"^24.0.0","tslint":"^5.14.0","typescript":"^3.3.4000"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"readme":"# react-model &middot; ![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) [![Build Status](https://travis-ci.org/byte-fe/react-model.svg?branch=master)](https://travis-ci.org/byte-fe/react-model) [![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⚔ Fully TypeScript Support\n\n📦 built with microbundle\n\n⚙️ Middlewares Pipline ( redux-devtools support ... )\n\n☂️ 100% test coverage, safe on production\n\n🐛 Debug easily on test environment\n\n```tsx\nimport { Model } from 'react-model'\n\n// define model\nconst Todo = {\n  state: {\n    items: ['Install react-model', 'Read github docs', 'Build App']\n  },\n  actions: {\n    add: todo => {\n      // s is the readonly version of state\n      // you can also return partial state here but don't need to keep immutable manually\n      // state is the mutable state\n      return state => state.items.push(todo)\n    }\n  }\n}\n\n// Model Register\nconst { useStore } = Model(Todo)\n\nconst App = () => {\n  return <TodoList />\n}\n\nconst TodoList = () => {\n  const [state, actions] = useStore()\n  return <div>\n    <Addon handler={actions.add} />\n    {state.items.map((item, index) => (<Todo key={index} item={item} />))}\n  </div>\n}\n```\n\n---\n\n## Quick Start\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\ninstall package\n\n```shell\nnpm install react-model\n```\n\n## Table of Contents\n\n- [Core Concept](#core-concept)\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- [Other Concept required by Class Component](#other-concept-required-by-class-component)\n  - [Provider](#provider)\n  - [connect](#connect)\n- [FAQ](#faq)\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  - [actions throw error from immer.module.js](#actions-throw-error-from-immer.module.js)\n\n## Core Concept\n\n### Model\n\nEvery model have their own state and actions.\n\n<details>\n<summary><del>old model</del> (will be deprecated in v3.0)</summary>\n<p>\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (state, _, params) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    },\n    openLight: async (state, actions) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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<typeof Model.actions>\n// type ConsumerType = { actions: ConsumerActionsType; state: StateType }\n// type ActionType = ConsumerActionsType\n// export { ConsumerType, StateType, ActionType }\n```\n</p>\n</details>\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: NextModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (payload, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    },\n    openLight: async (_, { state, actions }) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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(model)\n\n// You can use these types when use Class Components.\n// type ConsumerActionsType = getConsumerActionsType<typeof Model.actions>\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 keep the state and actions in the separate private store. 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 return from useStore can invoke the 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 () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button onClick={e => actions.increment(33)}>home increment</button>\n      <button onClick={e => sharedActions.increment(20)}>\n        shared increment\n      </button>\n      <button onClick={e => actions.get()}>fake request</button>\n      <button onClick={e => actions.openLight()}>fake nested call</button>\n    </div>\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 with depActions and React.memo to prevent child components rerender frequently.](https://github.com/ArrayZoneYour/react-model-todomvc/blob/master/src/components/TodoItem.tsx)\n\n[Demo Repo](https://github.com/ArrayZoneYour/react-model-todomvc)\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> Hint: The state returned should only be used as readonly\n\n```jsx\nimport { useStore, getState } from '../model/index'\n\nconst BasicHook = () => {\n  const [state, actions] = useStore('Counter')\n  useEffect(() => {\n    console.log('some mounted actions from BasicHooks')\n    return () =>\n      console.log(\n        `Basic Hooks unmounted, current Counter state: ${JSON.stringify(\n          getState('Counter')\n        )}`\n      )\n  }, [])\n  return (\n    <>\n      <div>state: {JSON.stringify(state)}</div>\n    </>\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: () => {\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 = () => {\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: NextModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (s, _, params) => {\n      // issue: https://github.com/Microsoft/TypeScript/issues/29196\n      // async function return produce need define type manually.\n      return (state: typeof s) => {\n        state.counter += params || 1\n      }\n    },\n    decrease: (s, _, params) => s => {\n      s.counter += params || 1\n    }\n  }\n}\n\nexport default Model(model)\n```\n\nJavaScript Example\n\n```js\nconst Model = {\n  actions: {\n    increment: async (s, _, params) => {\n      return state => {\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<details>\n<summary>Store: shared.ts</summary>\n<p>\n\n```ts\nconst initialState = {\n  counter: 0\n}\n\nconst model: NextModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: (params, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    }\n  },\n  // Provide for SSR\n  asyncState: async context => {\n    await waitFor(4000)\n    return { counter: 500 }\n  },\n  state: initialState\n}\n\nexport default Model(model)\n```\n\n</p>\n</details>\n\n<details>\n<summary>Global Config: _app.tsx</summary>\n<p>\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) => {\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    <Container>\n      <Component {...pageProps} />\n    </Container>\n  )\n}\n\nMyApp.getInitialProps = async (context: NextAppContext) => {\n  if (!(process as any).browser) {\n    const initialModels = context.Component.getInitialProps\n      ? await context.Component.getInitialProps(context.ctx)\n      await getInitialState() // get all model initialState\n      // : await getInitialState({ modelName: 'Home' }) // get Home initialState only\n      // : await getInitialState({ modelName: ['Home', 'Todo'] }) // get multi initialState\n      // : await getInitialState({ data }) // You can also pass some public data as asyncData params.\n    return { initialModels }\n  } else {\n    return { persistModel }\n  }\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Page: hooks/index.tsx</summary>\n<p>\n\n```tsx\nimport { useStore, getState } from '../index'\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button\n        onClick={e => {\n          actions.increment(33)\n        }}\n      >\n    </div>\n  )\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Single Page Config: benchmark.tsx</summary>\n<p>\n\n```tsx\n// ...\nBenchmark.getInitialProps = async () => {\n  return await getInitialState({ modelName: 'Todo' })\n}\n```\n</p>\n</details>\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<{}> = async (context, restMiddlewares) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: any) => 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) => async (params: any) => {\n  const context: Context = {\n    modelName,\n    setState,\n    actionName: action.name,\n    next: () => {},\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## 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      <Provider>\n        <Counter />\n      </Provider>\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<details>\n<summary>Javascript decorator version</summary>\n<p>\n\n```jsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\n\nconst mapProps = ({ light, counter }) => ({\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      <>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(5)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n      </>\n    )\n  }\n}\n```\n\n</p>\n</details>\n\n<details>\n<summary>TypeScript Version</summary>\n<p>\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) => ({\n  lightStatus: light ? 'open' : 'close',\n  counter,\n  response\n})\n\ntype RType = ReturnType<typeof mapProps>\n\nclass TSCounter extends PureComponent<\n  { state: RType } & { actions: ActionType }\n> {\n  render() {\n    const { state, actions } = this.props\n    return (\n      <>\n        <div>TS Counter</div>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(3)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n        <button onClick={e => actions.get()}>Get Response</button>\n        <div>message: {JSON.stringify(state.response)}</div>\n      </>\n    )\n  }\n}\n\nexport default connect(\n  'Home',\n  mapProps\n)(TSCounter)\n```\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\n\n## FAQ\n\n### How can I disable the console debugger\n\nJust remove consoleDebugger middleware.\n\n```typescript\nimport { actionMiddlewares } from 'react-model'\n// Find the index of middleware\nconst consoleDebuggerMiddlewareIndex = actionMiddlewares.indexOf(\n  middlewares.consoleDebugger\n)\n// Remove it\nactionMiddlewares.splice(consoleDebuggerMiddlewareIndex, 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) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: Error) => 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\nconst persistMiddleware: Middleware = async (context, restMiddlewares) => {\n  localStorage.setItem('__REACT_MODEL__', JSON.stringify(context.Global.State))\n  await context.next(restMiddlewares)\n}\n\nactionMiddlewares.push(persistMiddleware)\n\nModel({ Example }, JSON.parse(localStorage.getItem('__REACT_MODEL__')))\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      <div className=\"App\">\n        <Counter />\n        <Counter />\n        <Counter />\n      </div>\n    )\n  }\n}\n```\n\n<details>\n<summary>Counter model</summary>\n<p>\n\n```ts\ninterface State {\n  count: number\n}\n\ninterface ActionParams {\n  increment: number\n}\n\nconst model: NextModelType<State, ActionParams> = {\n  state: {\n    count: 0\n  },\n  actions: {\n    increment: payload => {\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 => (state.count += payload)\n      return state => {\n        state.count += payload\n      }\n    }\n  }\n}\n\n```\n\n</p>\n</details>\n\n<details>\n<summary>Counter.tsx</summary>\n<p>\n\n```tsx\n\nconst Counter = () => {\n  const [{ useStore }] = useState(() => Model(model))\n  const [state, actions] = useStore()\n  return (\n    <div>\n      <div>{state.count}</div>\n      <button onClick={() => actions.increment(3)}>Increment</button>\n    </div>\n  )\n}\n\nexport default Counter\n```\n\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\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 => {\n    // Not allowed\n    // return state => (state.count += payload)\n    return state => {\n      state.count += payload\n    }\n  }\n}\n```\n\n[⇧ back to top](#table-of-contents)","readmeFilename":"README.md","gitHead":"a09c1c594ad8d790ba04b2a9067c596ca71f8d62","_id":"react-model@2.7.1-alpha.2","_nodeVersion":"11.11.0","_npmVersion":"6.9.0","dist":{"integrity":"sha512-WB2z2nJuZrDuAM32Yu2rQSXCm4ysZjzpkQORozgEkB+Ykjmq4+kgY2saxuJfFMlevs6IWExHBfrZL0Q60yfKtg==","shasum":"bec8c0a052bace0e5b48167165247ca340865cfd","tarball":"https://registry.npmjs.org/react-model/-/react-model-2.7.1-alpha.2.tgz","fileCount":21,"unpackedSize":75298,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJcwFnUCRA9TVsSAnZWagAApWEP/A6woZzjKou+F8tRFB9z\n1ctHrjHy6qQLNwIUf+I/TFdc7wKWTv2+E5sKjJl0Cr+JEEp58shrdzYs9t7/\nagFHwgVsey6wj18fRIDhtn0S36V2XwIkZoH5VWvcSY6HdHdu6jmST30tivKh\nCH8UavsEGyq4HeNigJcB32H92HhmRQbx7JOeq/ZPsxv3BEqOsdVNfhqG6Hyn\nQRXsdzXbtqqIFIKUihaaIb2KC1+cl10CleKV0i8hDeSQh4E1XAgWQSR0qCkH\njd0gWWygC5lAlthjniVQnC4LAP+ReOT9sTzdNocAsc0C33oOnsmmo4xrZEGg\nKJoe4b2epp4VWGvUHiSM8gxH1twBS3PaUCZ9qL3A2Jo/04dZllqINzup28Mc\nKgOTNZ/pUl6216i8CwqvrwrW6xyQY3TCpVEioE9qoz9Rdqk6ZFsaW2V/qARS\n1jEl71y2nTpc7b8yJ9kifx5DliENxc/Z3WJf6Rl+Hsv1i8lOZyXXRJdE1nec\n9Cr/0JDIIGMSNxMdWvfgX9lyTfnnfZCzrlA5Y0+OMimIU88xgWU/RoUTzqry\n3/wAMoI6pQBdliDjryHjRN1YJOTkjPtbPzKU3FbfkpyzIgCgJJ/yXi6MTGlc\nLOprjNh5knMBPVlbf1HIuJyiCi818NoJ7z6oUyLf2DjqNLqhjBBB3SMtzMlr\nXUOm\r\n=cCNc\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIAk2hHOSp6hxBL0cQhE1/hoLdVvfWRF5yfsKypt8F/5oAiAd9uD4wVhvPvUiT2a70aPAicWyTiYqmKaxbcSYaBlitg=="}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_2.7.1-alpha.2_1556109779231_0.44487112245795846"},"_hasShrinkwrap":false},"2.7.1":{"name":"react-model","version":"2.7.1","description":"The State management library for React","main":"./dist/react-model.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^2.0.0"},"peerDependencies":{"react":"^16.3.0","react-dom":"^16.3.0"},"devDependencies":{"@commitlint/cli":"^7.5.2","@commitlint/config-conventional":"^7.5.0","@types/jest":"^24.0.5","@types/node":"^11.9.4","@types/react":"^16.8.2","@types/react-dom":"^16.8.0","commitizen":"^3.0.7","cz-conventional-changelog":"^2.1.0","husky":"^1.3.1","jest":"^24.1.0","prettier":"^1.16.4","react":"^16.8.4","react-dom":"^16.8.4","react-hooks-testing-library":"^0.4.0","react-testing-library":"^6.0.0","remark-cli":"^6.0.1","remark-lint":"^6.0.4","remark-preset-lint-recommended":"^3.0.2","ts-jest":"^24.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"gitHead":"7f481adad5056f920a9b78c33cf6c93c8abbbf0a","_id":"react-model@2.7.1","_nodeVersion":"10.15.0","_npmVersion":"6.9.0","dist":{"integrity":"sha512-u/+0qWouO/OdT2bRpRSUhajMTJqO2ejXj4RhVOAf0uFQkq0AavbxA0mFebkb0RDjVpqR6RzHdYZxJ8RSUOSOPg==","shasum":"35b2ca866286ccd4b85af0ffa92bdad4c0a52dfb","tarball":"https://registry.npmjs.org/react-model/-/react-model-2.7.1.tgz","fileCount":21,"unpackedSize":75470,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJcwG/zCRA9TVsSAnZWagAAwsUP/ia6VSxj0YCQ75wsxcJU\nPfgxu4O3rr9iZfb+ZdP9zWgMNTMlHiqj6Kf5h3BulsKDk6aEIYUSRrFqFYes\nXMN10DjFSZaWiC0ZRI07ESM1N9A2AD06uc/h9I7GcjgPg/A3IjCTYZeD+ED0\n00qGLoDJb1ZuuV3xWgA38JOShoQh1bGdM1cvOOtM5sOYXfRYs2Ysw1Al/WPY\nA62CS7sth5zdU111wymI+S8y4rblTDrzd3nKuou7I1UxtxX07ETxhSudJnoY\nmRk9tEzLoUjXM9SCaEwa/fev/T0zDN7IwzdXlIK5hSTFzL/0J7OZ6Qrb+1i4\nVERB9ARBa+1FmAtL4pTN5uaprl1Z9Na8ObVwb0uNIem9pTmHSFOu9/gOA8Li\n1hs1VSu7xBNBID13JYZUagvJT6GbbkczIGzPCayNA8sbBmweTq0+kU2EEDSj\ncv3WujmcnGbd9TUDIDrUGAoYiQrHdaw6vU9aJhlkiy3fZtpcR6r5pyUWt01P\n/WgPaRskzdDlkWycFy6r2FIonjHQzhLlVKS3Pt1NQ6EleQN/fthqUMsX9a2K\nGZFZiqiKzdmUIbhbY6yuGiA8kGCa4h9N7Nb7CDzNWdY/a6BgfMkqznQRro8Y\nYW7XsZfBeieitz+uXMxj79fULxjqmOYnxkz9PGMYHOTkuiI61Cny1zFBFL8V\n5T3Z\r\n=WafG\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCdHicsguldRmohXgk0aE5JSAbGirZkkARHu2EE1rD1qwIgDd8LhWoahZT7i3tTJu6Q4kMQYvoFwj0pABn8WiQNmtA="}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_2.7.1_1556115442878_0.4639476437244081"},"_hasShrinkwrap":false},"2.7.2-alpha.0":{"name":"react-model","version":"2.7.2-alpha.0","description":"The State management library for React","main":"./dist/react-model.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^2.0.0"},"peerDependencies":{"react":"^16.3.0","react-dom":"^16.3.0"},"devDependencies":{"@commitlint/cli":"^7.5.2","@commitlint/config-conventional":"^7.5.0","@types/jest":"^24.0.5","@types/node":"^11.9.4","@types/react":"^16.8.2","@types/react-dom":"^16.8.0","commitizen":"^3.0.7","cz-conventional-changelog":"^2.1.0","husky":"^2.0.0","jest":"^24.1.0","prettier":"^1.16.4","react":"^16.8.4","react-dom":"^16.8.4","react-hooks-testing-library":"^0.4.0","react-testing-library":"^6.0.0","remark-cli":"^6.0.1","remark-lint":"^6.0.4","remark-preset-lint-recommended":"^3.0.2","ts-jest":"^24.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"readme":"# react-model &middot; ![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) [![Build Status](https://travis-ci.org/byte-fe/react-model.svg?branch=master)](https://travis-ci.org/byte-fe/react-model) [![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⚔ Fully TypeScript Support\n\n📦 built with microbundle\n\n⚙️ Middlewares Pipline ( redux-devtools support ... )\n\n☂️ 100% test coverage, safe on production\n\n🐛 Debug easily on test environment\n\n```tsx\nimport { Model } from 'react-model'\n\n// define model\nconst Todo = {\n  state: {\n    items: ['Install react-model', 'Read github docs', 'Build App']\n  },\n  actions: {\n    add: todo => {\n      // s is the readonly version of state\n      // you can also return partial state here but don't need to keep immutable manually\n      // state is the mutable state\n      return state => state.items.push(todo)\n    }\n  }\n}\n\n// Model Register\nconst { useStore } = Model(Todo)\n\nconst App = () => {\n  return <TodoList />\n}\n\nconst TodoList = () => {\n  const [state, actions] = useStore()\n  return <div>\n    <Addon handler={actions.add} />\n    {state.items.map((item, index) => (<Todo key={index} item={item} />))}\n  </div>\n}\n```\n\n---\n\n## Quick Start\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\ninstall package\n\n```shell\nnpm install react-model\n```\n\n## Table of Contents\n\n- [Core Concept](#core-concept)\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- [Other Concept required by Class Component](#other-concept-required-by-class-component)\n  - [Provider](#provider)\n  - [connect](#connect)\n- [FAQ](#faq)\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  - [actions throw error from immer.module.js](#actions-throw-error-from-immer.module.js)\n  - [How can I customize each model's middlewares?](#how-can-i-customize-each-model's-middlewares)\n\n## Core Concept\n\n### Model\n\nEvery model have their own state and actions.\n\n<details>\n<summary><del>old model</del> (will be deprecated in v3.0)</summary>\n<p>\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (state, _, params) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    },\n    openLight: async (state, actions) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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<typeof Model.actions>\n// type ConsumerType = { actions: ConsumerActionsType; state: StateType }\n// type ActionType = ConsumerActionsType\n// export { ConsumerType, StateType, ActionType }\n```\n</p>\n</details>\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: NextModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (payload, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    },\n    openLight: async (_, { state, actions }) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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(model)\n\n// You can use these types when use Class Components.\n// type ConsumerActionsType = getConsumerActionsType<typeof Model.actions>\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 keep the state and actions in the separate private store. 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 return from useStore can invoke the 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 () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button onClick={e => actions.increment(33)}>home increment</button>\n      <button onClick={e => sharedActions.increment(20)}>\n        shared increment\n      </button>\n      <button onClick={e => actions.get()}>fake request</button>\n      <button onClick={e => actions.openLight()}>fake nested call</button>\n    </div>\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 with depActions and React.memo to prevent child components rerender frequently.](https://github.com/ArrayZoneYour/react-model-todomvc/blob/master/src/components/TodoItem.tsx)\n\n[Demo Repo](https://github.com/ArrayZoneYour/react-model-todomvc)\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> Hint: The state returned should only be used as readonly\n\n```jsx\nimport { useStore, getState } from '../model/index'\n\nconst BasicHook = () => {\n  const [state, actions] = useStore('Counter')\n  useEffect(() => {\n    console.log('some mounted actions from BasicHooks')\n    return () =>\n      console.log(\n        `Basic Hooks unmounted, current Counter state: ${JSON.stringify(\n          getState('Counter')\n        )}`\n      )\n  }, [])\n  return (\n    <>\n      <div>state: {JSON.stringify(state)}</div>\n    </>\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: () => {\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 = () => {\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: NextModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (params, { state: s }) => {\n      // issue: https://github.com/Microsoft/TypeScript/issues/29196\n      // async function return produce need define type manually.\n      return (state: typeof s) => {\n        state.counter += params || 1\n      }\n    },\n    decrease: params => s => {\n      s.counter += params || 1\n    }\n  }\n}\n\nexport default Model(model)\n```\n\nJavaScript Example\n\n```js\nconst Model = {\n  actions: {\n    increment: async params => {\n      return state => {\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<details>\n<summary>Store: shared.ts</summary>\n<p>\n\n```ts\nconst initialState = {\n  counter: 0\n}\n\nconst model: NextModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: (params, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    }\n  },\n  // Provide for SSR\n  asyncState: async context => {\n    await waitFor(4000)\n    return { counter: 500 }\n  },\n  state: initialState\n}\n\nexport default Model(model)\n```\n\n</p>\n</details>\n\n<details>\n<summary>Global Config: _app.tsx</summary>\n<p>\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) => {\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    <Container>\n      <Component {...pageProps} />\n    </Container>\n  )\n}\n\nMyApp.getInitialProps = async (context: NextAppContext) => {\n  if (!(process as any).browser) {\n    const initialModels = context.Component.getInitialProps\n      ? await context.Component.getInitialProps(context.ctx)\n      await getInitialState() // get all model initialState\n      // : await getInitialState({ modelName: 'Home' }) // get Home initialState only\n      // : await getInitialState({ modelName: ['Home', 'Todo'] }) // get multi initialState\n      // : await getInitialState({ data }) // You can also pass some public data as asyncData params.\n    return { initialModels }\n  } else {\n    return { persistModel }\n  }\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Page: hooks/index.tsx</summary>\n<p>\n\n```tsx\nimport { useStore, getState } from '../index'\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button\n        onClick={e => {\n          actions.increment(33)\n        }}\n      >\n    </div>\n  )\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Single Page Config: benchmark.tsx</summary>\n<p>\n\n```tsx\n// ...\nBenchmark.getInitialProps = async () => {\n  return await getInitialState({ modelName: 'Todo' })\n}\n```\n</p>\n</details>\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<{}> = async (context, restMiddlewares) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: any) => 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) => async (params: any) => {\n  const context: Context = {\n    modelName,\n    setState,\n    actionName: action.name,\n    next: () => {},\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## 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      <Provider>\n        <Counter />\n      </Provider>\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<details>\n<summary>Javascript decorator version</summary>\n<p>\n\n```jsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\n\nconst mapProps = ({ light, counter }) => ({\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      <>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(5)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n      </>\n    )\n  }\n}\n```\n\n</p>\n</details>\n\n<details>\n<summary>TypeScript Version</summary>\n<p>\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) => ({\n  lightStatus: light ? 'open' : 'close',\n  counter,\n  response\n})\n\ntype RType = ReturnType<typeof mapProps>\n\nclass TSCounter extends PureComponent<\n  { state: RType } & { actions: ActionType }\n> {\n  render() {\n    const { state, actions } = this.props\n    return (\n      <>\n        <div>TS Counter</div>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(3)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n        <button onClick={e => actions.get()}>Get Response</button>\n        <div>message: {JSON.stringify(state.response)}</div>\n      </>\n    )\n  }\n}\n\nexport default connect(\n  'Home',\n  mapProps\n)(TSCounter)\n```\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\n\n## FAQ\n\n### How can I disable the console debugger\n\nJust remove consoleDebugger middleware.\n\n```typescript\nimport { actionMiddlewares } from 'react-model'\n// Find the index of middleware\nconst consoleDebuggerMiddlewareIndex = actionMiddlewares.indexOf(\n  middlewares.consoleDebugger\n)\n// Remove it\nactionMiddlewares.splice(consoleDebuggerMiddlewareIndex, 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) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: Error) => 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\nconst persistMiddleware: Middleware = async (context, restMiddlewares) => {\n  localStorage.setItem('__REACT_MODEL__', JSON.stringify(context.Global.State))\n  await context.next(restMiddlewares)\n}\n\nactionMiddlewares.push(persistMiddleware)\n\nModel({ Example }, JSON.parse(localStorage.getItem('__REACT_MODEL__')))\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      <div className=\"App\">\n        <Counter />\n        <Counter />\n        <Counter />\n      </div>\n    )\n  }\n}\n```\n\n<details>\n<summary>Counter model</summary>\n<p>\n\n```ts\ninterface State {\n  count: number\n}\n\ninterface ActionParams {\n  increment: number\n}\n\nconst model: NextModelType<State, ActionParams> = {\n  state: {\n    count: 0\n  },\n  actions: {\n    increment: payload => {\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 => (state.count += payload)\n      return state => {\n        state.count += payload\n      }\n    }\n  }\n}\n\n```\n\n</p>\n</details>\n\n<details>\n<summary>Counter.tsx</summary>\n<p>\n\n```tsx\n\nconst Counter = () => {\n  const [{ useStore }] = useState(() => Model(model))\n  const [state, actions] = useStore()\n  return (\n    <div>\n      <div>{state.count}</div>\n      <button onClick={() => actions.increment(3)}>Increment</button>\n    </div>\n  )\n}\n\nexport default Counter\n```\n\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\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 => {\n    // Not allowed\n    // return state => (state.count += payload)\n    return state => {\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\nIf you are using NextModel, you can customize each model's middlewares.\n\n```typescript\nimport { actionMiddlewares, Model } from 'react-model'\nimport { delayMiddleware } from './middlewares'\n\nconst nextCounterModel: NextModelType<CounterState, NextCounterActionParams> = {\n  actions: {\n    add: num => {\n      return state => {\n        state.count += num\n      }\n    },\n    increment: async (num, { actions }) => {\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","readmeFilename":"README.md","gitHead":"0ea86fd28bf0e39c4c701b314c4cd9fa6750a362","_id":"react-model@2.7.2-alpha.0","_npmVersion":"6.4.1","_nodeVersion":"10.15.3","_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"dist":{"integrity":"sha512-Ng/6qw7VvGUacje6Kd1oxa7iwA6g+iM2QBq5nYhPY6ZJa86EkEmAnuoD40MtkMz3QmT2tTOFWRuEWPzwhu+ywg==","shasum":"820bfbda36ea81731a0cb9277da680651dfe6e4d","tarball":"https://registry.npmjs.org/react-model/-/react-model-2.7.2-alpha.0.tgz","fileCount":21,"unpackedSize":76411,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJc27C+CRA9TVsSAnZWagAAV8MP/Rb7Cz0KaIcd3unlJjfP\nm5DwTJ9fzNG+WhfukYWx+zzFKX0DeCc/b9IcGbYP9r9JF8YV4JxJZXzPlAaF\nJs8Oce4EeufFMUKkCv1xcxPz8uK9/l0HB7or2svcF+xPn/V9JNKoWM6PvM/m\nXLdDfEJ19+4oSJk8hj2lVb6nX/NWYEiIPD0YGnDIKZ9LGfPpdODvnRrmLgYu\nMfSqRcyo7aegGy0KKJIWLsaetjHENGuxVhIlKK3aIp9hhlwinlEbnE8MNuzl\nzYgmJKtNi3F4nP8D3Lv1pbAy+F86p1sMJEl1DJosbdSrneXWsnWSqXYro/rC\nwyT6eXeHx1kfi/TQZYD+A597zZ83/MdCtTOT6PYsoa1TlwoLbRLGjcWVXqa/\n82bRcdxTwOr454eWac1oQLmRKulfqM4MVkKSEs6JSYV0vYc0F5PiwTlvdDRg\n3W87dKPqg8Srh445Jv7xyqdbHZzjWu15dBWooSxFZBrnHmdpwTDxNE52CWHq\nYYmPlL+/EzkZZSoeQk4X2SjHwgRPnP2Mdqu6uEApmrqqhIx4jZEweZycew0V\nA2iC9z3HiCO/+gn3idOB/EMjCaif6EXhOBPC675ncWM3+ierxHvaPlMRNxhH\nyhwCZX2OUWos7Hyw8SfriZWY3X8iIFvbP8pEU/gmOK5gzL0lGn9EM9gxgNd7\nkXVw\r\n=EaPl\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIAUrRdPEQ2SohcJ14uOcgYGbxr45xGT35M/xsGYDUzp9AiBtVINTsWtqJUSs8yPdUyFYcKy7gBqB8kAF5qghpz3rxg=="}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_2.7.2-alpha.0_1557901501248_0.3325401782014854"},"_hasShrinkwrap":false},"2.7.2":{"name":"react-model","version":"2.7.2","description":"The State management library for React","main":"./dist/react-model.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^2.0.0"},"peerDependencies":{"react":"^16.3.0","react-dom":"^16.3.0"},"devDependencies":{"@commitlint/cli":"^7.5.2","@commitlint/config-conventional":"^7.5.0","@types/jest":"^24.0.5","@types/node":"^11.9.4","@types/react":"^16.8.2","@types/react-dom":"^16.8.0","commitizen":"^3.0.7","cz-conventional-changelog":"^2.1.0","husky":"^2.0.0","jest":"^24.1.0","prettier":"^1.16.4","react":"^16.8.4","react-dom":"^16.8.4","react-hooks-testing-library":"^0.4.0","react-testing-library":"^6.0.0","remark-cli":"^6.0.1","remark-lint":"^6.0.4","remark-preset-lint-recommended":"^3.0.2","ts-jest":"^24.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"gitHead":"0ea86fd28bf0e39c4c701b314c4cd9fa6750a362","_id":"react-model@2.7.2","_npmVersion":"6.4.1","_nodeVersion":"10.15.3","_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"dist":{"integrity":"sha512-K/XkTHak2gvfYfJI3USrGBRdn3YgbpedGtfNyIRpq2xDGORgivrwhfvDlyuWm69CkMl/DVzCIyhSFDqyP53Ghw==","shasum":"4876e69ebd8081b044c1122f35e23ba7ced29c32","tarball":"https://registry.npmjs.org/react-model/-/react-model-2.7.2.tgz","fileCount":21,"unpackedSize":76403,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJc27bwCRA9TVsSAnZWagAAsD8P+wZzSJdZjDHycC9iqOJc\ntXkt4Hyd4WKEGJLNgvbbk4h2A1LO+K5WRSMAj5t774D+XwifIY3WI4L3rRM5\nzq9nhF4Pi3JbsfcSEAKTAlGgue75+fxAyCyL/BaotmfTIoHOh3hOq4QUhtKx\nsnN47O2HV//3NyrxnEP4BcnKMDQFQvXcvC7nk4F0cTobTsu1SPjkKrxIyV7p\nLKHSt5Vx1vAYDdeCeB+JO30jvOUGVk3BP5CdOXUvGCbjp+u3hkT1oY39G+V7\n0tw50UgjCzpxCAECUltZrV231tW1z20jfPoV9nGNtNEVyTp0MHNHn564j16T\npkicMYo7LpfzU4RaFEr/+iZWbMrgIsey8D52+CuIDLnEiCT+PR9knPCH1dzs\nIkksyy+sO0ONCy1xi/OvWyaSnaCbTfLEguZScoRLoyjy5JiXbaLN+XxNGAeZ\nFNMReon9yi9SaJHGrtLGJicxnR7Ce+kytFnlBXV5orI48fRl4Enq/h27xU0b\n0tKhY6eeQ+7rTjw6kiuCIUydp4JAJLaZ7kQazIkfr1fikPcslNemP+PZRnTt\nHIhHvViFzSiSgoRX51Rr5YQEEW4OkSy7DJohxQeAm60gtjYPWQ6dg43JRND9\n3B2PzAjPrAYcIUScn0v14/03q9lNXAI7Wsje7I4okfJWayOPy3F+ovC19TXE\nBk6f\r\n=kAA9\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDq816g2/5FFx0ens8MW0DiGAcKwmoliaBxFmnC0LIH3QIgUUGBdNut9baOAVAcBUiahU/6MFk+pEToiLoJZgp2Ywc="}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_2.7.2_1557903087913_0.619295465018872"},"_hasShrinkwrap":false},"3.0.0-alpha.2":{"name":"react-model","version":"3.0.0-alpha.2","description":"The State management library for React","main":"./dist/react-model.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^3.0.0"},"peerDependencies":{"react":"^16.3.0","react-dom":"^16.3.0"},"devDependencies":{"@commitlint/cli":"^7.5.2","@commitlint/config-conventional":"^7.5.0","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/jest":"^24.0.5","@types/node":"^12.0.0","@types/react":"^16.8.2","@types/react-dom":"^16.8.0","commitizen":"^3.0.7","cz-conventional-changelog":"^2.1.0","husky":"^2.0.0","jest":"^24.1.0","prettier":"^1.16.4","react":"^16.8.4","react-dom":"^16.8.4","react-hooks-testing-library":"^0.5.0","react-test-renderer":"^16.8.6","react-testing-library":"^7.0.0","remark-cli":"^6.0.1","remark-lint":"^6.0.4","remark-preset-lint-recommended":"^3.0.2","ts-jest":"^24.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"readme":"# react-model &middot; ![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) [![Build Status](https://travis-ci.org/byte-fe/react-model.svg?branch=master)](https://travis-ci.org/byte-fe/react-model) [![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⚔ Fully TypeScript Support\n\n📦 built with microbundle\n\n⚙️ Middlewares Pipline ( redux-devtools support ... )\n\n☂️ 100% test coverage, safe on production\n\n🐛 Debug easily on test environment\n\n```tsx\nimport { Model } from 'react-model'\n\n// define model\nconst Todo = {\n  state: {\n    items: ['Install react-model', 'Read github docs', 'Build App']\n  },\n  actions: {\n    add: todo => {\n      // s is the readonly version of state\n      // you can also return partial state here but don't need to keep immutable manually\n      // state is the mutable state\n      return state => {\n        state.items.push(todo)\n      }\n    }\n  }\n}\n\n// Model Register\nconst { useStore } = Model(Todo)\n\nconst App = () => {\n  return <TodoList />\n}\n\nconst TodoList = () => {\n  const [state, actions] = useStore()\n  return <div>\n    <Addon handler={actions.add} />\n    {state.items.map((item, index) => (<Todo key={index} item={item} />))}\n  </div>\n}\n```\n\n---\n\n## Quick Start\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  - [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- [Other Concept required by Class Component](#other-concept-required-by-class-component)\n  - [Provider](#provider)\n  - [connect](#connect)\n- [FAQ](#faq)\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  - [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### Model\n\nEvery model have their 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: NextModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (payload, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    },\n    openLight: async (_, { state, actions }) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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(model)\n\n// You can use these types when use Class Components.\n// type ConsumerActionsType = getConsumerActionsType<typeof Model.actions>\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 keep the state and actions in the separate private store. 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 return from useStore can invoke the 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 () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button onClick={e => actions.increment(33)}>home increment</button>\n      <button onClick={e => sharedActions.increment(20)}>\n        shared increment\n      </button>\n      <button onClick={e => actions.get()}>fake request</button>\n      <button onClick={e => actions.openLight()}>fake nested call</button>\n    </div>\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 with depActions and React.memo to prevent child components rerender frequently.](https://github.com/ArrayZoneYour/react-model-todomvc/blob/master/src/components/TodoItem.tsx)\n\n[Demo Repo](https://github.com/ArrayZoneYour/react-model-todomvc)\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> Hint: The state returned should only be used as readonly\n\n```jsx\nimport { useStore, getState } from '../model/index'\n\nconst BasicHook = () => {\n  const [state, actions] = useStore('Counter')\n  useEffect(() => {\n    console.log('some mounted actions from BasicHooks')\n    return () =>\n      console.log(\n        `Basic Hooks unmounted, current Counter state: ${JSON.stringify(\n          getState('Counter')\n        )}`\n      )\n  }, [])\n  return (\n    <>\n      <div>state: {JSON.stringify(state)}</div>\n    </>\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: () => {\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 = () => {\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: NextModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (s, _, params) => {\n      // issue: https://github.com/Microsoft/TypeScript/issues/29196\n      // async function return produce need define type manually.\n      return (state: typeof s) => {\n        state.counter += params || 1\n      }\n    },\n    decrease: (s, _, params) => s => {\n      s.counter += params || 1\n    }\n  }\n}\n\nexport default Model(model)\n```\n\nJavaScript Example\n\n```js\nconst Model = {\n  actions: {\n    increment: async (s, _, params) => {\n      return state => {\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<details>\n<summary>Store: shared.ts</summary>\n<p>\n\n```ts\nconst initialState = {\n  counter: 0\n}\n\nconst model: NextModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: (params, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    }\n  },\n  // Provide for SSR\n  asyncState: async context => {\n    await waitFor(4000)\n    return { counter: 500 }\n  },\n  state: initialState\n}\n\nexport default Model(model)\n```\n\n</p>\n</details>\n\n<details>\n<summary>Global Config: _app.tsx</summary>\n<p>\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) => {\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    <Container>\n      <Component {...pageProps} />\n    </Container>\n  )\n}\n\nMyApp.getInitialProps = async (context: NextAppContext) => {\n  if (!(process as any).browser) {\n    const initialModels = context.Component.getInitialProps\n      ? await context.Component.getInitialProps(context.ctx)\n      await getInitialState() // get all model initialState\n      // : await getInitialState({ modelName: 'Home' }) // get Home initialState only\n      // : await getInitialState({ modelName: ['Home', 'Todo'] }) // get multi initialState\n      // : await getInitialState({ data }) // You can also pass some public data as asyncData params.\n    return { initialModels }\n  } else {\n    return { persistModel }\n  }\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Page: hooks/index.tsx</summary>\n<p>\n\n```tsx\nimport { useStore, getState } from '../index'\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button\n        onClick={e => {\n          actions.increment(33)\n        }}\n      >\n    </div>\n  )\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Single Page Config: benchmark.tsx</summary>\n<p>\n\n```tsx\n// ...\nBenchmark.getInitialProps = async () => {\n  return await getInitialState({ modelName: 'Todo' })\n}\n```\n</p>\n</details>\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<{}> = async (context, restMiddlewares) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: any) => 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) => async (params: any) => {\n  const context: Context = {\n    modelName,\n    setState,\n    actionName: action.name,\n    next: () => {},\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## 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      <Provider>\n        <Counter />\n      </Provider>\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<details>\n<summary>Javascript decorator version</summary>\n<p>\n\n```jsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\n\nconst mapProps = ({ light, counter }) => ({\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      <>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(5)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n      </>\n    )\n  }\n}\n```\n\n</p>\n</details>\n\n<details>\n<summary>TypeScript Version</summary>\n<p>\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) => ({\n  lightStatus: light ? 'open' : 'close',\n  counter,\n  response\n})\n\ntype RType = ReturnType<typeof mapProps>\n\nclass TSCounter extends PureComponent<\n  { state: RType } & { actions: ActionType }\n> {\n  render() {\n    const { state, actions } = this.props\n    return (\n      <>\n        <div>TS Counter</div>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(3)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n        <button onClick={e => actions.get()}>Get Response</button>\n        <div>message: {JSON.stringify(state.response)}</div>\n      </>\n    )\n  }\n}\n\nexport default connect(\n  'Home',\n  mapProps\n)(TSCounter)\n```\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\n\n## FAQ\n\n### How can I disable the console debugger\n\nJust remove consoleDebugger middleware.\n\n```typescript\nimport { actionMiddlewares } from 'react-model'\n// Find the index of middleware\nconst consoleDebuggerMiddlewareIndex = actionMiddlewares.indexOf(\n  middlewares.consoleDebugger\n)\n// Remove it\nactionMiddlewares.splice(consoleDebuggerMiddlewareIndex, 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) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: Error) => 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\nconst persistMiddleware: Middleware = async (context, restMiddlewares) => {\n  localStorage.setItem('__REACT_MODEL__', JSON.stringify(context.Global.State))\n  await context.next(restMiddlewares)\n}\n\nactionMiddlewares.push(persistMiddleware)\n\nModel({ Example }, JSON.parse(localStorage.getItem('__REACT_MODEL__')))\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      <div className=\"App\">\n        <Counter />\n        <Counter />\n        <Counter />\n      </div>\n    )\n  }\n}\n```\n\n<details>\n<summary>Counter model</summary>\n<p>\n\n```ts\ninterface State {\n  count: number\n}\n\ninterface ActionParams {\n  increment: number\n}\n\nconst model: NextModelType<State, ActionParams> = {\n  state: {\n    count: 0\n  },\n  actions: {\n    increment: payload => {\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 => (state.count += payload)\n      return state => {\n        state.count += payload\n      }\n    }\n  }\n}\n\n```\n\n</p>\n</details>\n\n<details>\n<summary>Counter.tsx</summary>\n<p>\n\n```tsx\n\nconst Counter = () => {\n  const [{ useStore }] = useState(() => Model(model))\n  const [state, actions] = useStore()\n  return (\n    <div>\n      <div>{state.count}</div>\n      <button onClick={() => actions.increment(3)}>Increment</button>\n    </div>\n  )\n}\n\nexport default Counter\n```\n\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\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 => {\n    // Not allowed\n    // return state => (state.count += payload)\n    return state => {\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\nIf you are using NextModel, you can customize each model's middlewares.\n\n```typescript\nimport { actionMiddlewares, Model } from 'react-model'\nimport { delayMiddleware } from './middlewares'\n\nconst nextCounterModel: NextModelType<CounterState, NextCounterActionParams> = {\n  actions: {\n    add: num => {\n      return state => {\n        state.count += num\n      }\n    },\n    increment: async (num, { actions }) => {\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","readmeFilename":"README.md","gitHead":"fbc40b4cec928b579acbfe722d78d61207fb739d","_id":"react-model@3.0.0-alpha.2","_npmVersion":"6.4.1","_nodeVersion":"8.12.0","_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"dist":{"integrity":"sha512-ZS0vhUiyVtPMoQ3NwyH5Sj3vLPKqD7VPToy+RxoSJA/19qzoytcxEexXsb81GC8hyBokxhTDlEDsTEZvChayLA==","shasum":"9cffd6d8609ae57f07ac71b92fc1eaf592f2a204","tarball":"https://registry.npmjs.org/react-model/-/react-model-3.0.0-alpha.2.tgz","fileCount":21,"unpackedSize":74046,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJc66NZCRA9TVsSAnZWagAA4PwQAI/kyRr3f43onQaPVfgX\nvmAkjaN2WVGL0VEH2HYzQ3AE3nKLzKi5S7s/tmJ84pcgYK5I/agelOCyuwJc\nEZi0zUUnd5AEJJKOf2mzsLgmrJ2rYk0dqDMfBM7xxL+wydLEijtRoH/MxV/s\ndGyLNA60yhI+/3Tub0jbbc1JGxdXvZfxsODXwx6/fPAG2i1SQGVJkXp2bqZJ\n2aa89noLQYPlusNrWPRkPkpGJwhIMDKNvpVeEh7UGF3DNqRyc79WkA+Sw0IJ\n4zydRW/RhWhhhmr6f9avfjvY3VIpOHntn6JFmFTr/zi3BR3shnhKc1wkkUOH\nysDRqWCmf1cSlDE7/izuxeU3AB8mmS24edkNe8g65hS9d1VIqE+XLSnbMDoC\nWjNtXjPEdtr4ZRWBLEIKNiB0hvAtsJdaAn3tvTy8xm3DFICkK/0PFGw+Urun\n3PaO52mMi9TcRFera4jpOSroO5G5uMm6aVOpQIzazIAKEW0go3vSXacfruOL\nnwO6UcwSLmSgqReTOQ5vEWSwo1WVhtBZyUerNXdX5ICDgXm9d4i53sgw63rL\nA9J/7FmhA17V9tVkInZM8qUTljusJ68IJJiFBZQCUa8b1LaTq0g0b51tWDTW\nItAs0BC4lKwzp0sLKye25Rrc2fvdVL3W5b0ws9vIHuCaN6VrPRlzgiYWmG26\nj1nE\r\n=eH1n\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQCmwAW+p/89+m4nWEwD56wqwUsdKb/Nm72Pk3F7ndRaFAIhAMgpIm34feZc1AV//zTD+4a4UwWDImZCHJYgn4SVR9kN"}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_3.0.0-alpha.2_1558946648270_0.22278458067986917"},"_hasShrinkwrap":false},"3.0.0-alpha.3":{"name":"react-model","version":"3.0.0-alpha.3","description":"The State management library for React","main":"./dist/react-model.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^3.0.0"},"peerDependencies":{"react":"^16.3.0","react-dom":"^16.3.0"},"devDependencies":{"@commitlint/cli":"^7.5.2","@commitlint/config-conventional":"^7.5.0","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/jest":"^24.0.5","@types/node":"^12.0.0","@types/react":"^16.8.2","@types/react-dom":"^16.8.0","commitizen":"^3.0.7","cz-conventional-changelog":"^2.1.0","husky":"^2.0.0","jest":"^24.1.0","prettier":"^1.16.4","react":"^16.8.4","react-dom":"^16.8.4","react-hooks-testing-library":"^0.5.0","react-test-renderer":"^16.8.6","react-testing-library":"^7.0.0","remark-cli":"^6.0.1","remark-lint":"^6.0.4","remark-preset-lint-recommended":"^3.0.2","ts-jest":"^24.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"readme":"# react-model &middot; ![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) [![Build Status](https://travis-ci.org/byte-fe/react-model.svg?branch=master)](https://travis-ci.org/byte-fe/react-model) [![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⚔ Fully TypeScript Support\n\n📦 built with microbundle\n\n⚙️ Middlewares Pipline ( redux-devtools support ... )\n\n☂️ 100% test coverage, safe on production\n\n🐛 Debug easily on test environment\n\n```tsx\nimport { Model } from 'react-model'\n\n// define model\nconst Todo = {\n  state: {\n    items: ['Install react-model', 'Read github docs', 'Build App']\n  },\n  actions: {\n    add: todo => {\n      // s is the readonly version of state\n      // you can also return partial state here but don't need to keep immutable manually\n      // state is the mutable state\n      return state => {\n        state.items.push(todo)\n      }\n    }\n  }\n}\n\n// Model Register\nconst { useStore } = Model(Todo)\n\nconst App = () => {\n  return <TodoList />\n}\n\nconst TodoList = () => {\n  const [state, actions] = useStore()\n  return <div>\n    <Addon handler={actions.add} />\n    {state.items.map((item, index) => (<Todo key={index} item={item} />))}\n  </div>\n}\n```\n\n---\n\n## Quick Start\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  - [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- [Other Concept required by Class Component](#other-concept-required-by-class-component)\n  - [Provider](#provider)\n  - [connect](#connect)\n- [FAQ](#faq)\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  - [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### Model\n\nEvery model have their 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: NextModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (payload, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    },\n    openLight: async (_, { state, actions }) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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(model)\n\n// You can use these types when use Class Components.\n// type ConsumerActionsType = getConsumerActionsType<typeof Model.actions>\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 keep the state and actions in the separate private store. 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 return from useStore can invoke the 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 () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button onClick={e => actions.increment(33)}>home increment</button>\n      <button onClick={e => sharedActions.increment(20)}>\n        shared increment\n      </button>\n      <button onClick={e => actions.get()}>fake request</button>\n      <button onClick={e => actions.openLight()}>fake nested call</button>\n    </div>\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 with depActions and React.memo to prevent child components rerender frequently.](https://github.com/ArrayZoneYour/react-model-todomvc/blob/master/src/components/TodoItem.tsx)\n\n[Demo Repo](https://github.com/ArrayZoneYour/react-model-todomvc)\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> Hint: The state returned should only be used as readonly\n\n```jsx\nimport { useStore, getState } from '../model/index'\n\nconst BasicHook = () => {\n  const [state, actions] = useStore('Counter')\n  useEffect(() => {\n    console.log('some mounted actions from BasicHooks')\n    return () =>\n      console.log(\n        `Basic Hooks unmounted, current Counter state: ${JSON.stringify(\n          getState('Counter')\n        )}`\n      )\n  }, [])\n  return (\n    <>\n      <div>state: {JSON.stringify(state)}</div>\n    </>\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: () => {\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 = () => {\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: NextModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (s, _, params) => {\n      // issue: https://github.com/Microsoft/TypeScript/issues/29196\n      // async function return produce need define type manually.\n      return (state: typeof s) => {\n        state.counter += params || 1\n      }\n    },\n    decrease: (s, _, params) => s => {\n      s.counter += params || 1\n    }\n  }\n}\n\nexport default Model(model)\n```\n\nJavaScript Example\n\n```js\nconst Model = {\n  actions: {\n    increment: async (s, _, params) => {\n      return state => {\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<details>\n<summary>Store: shared.ts</summary>\n<p>\n\n```ts\nconst initialState = {\n  counter: 0\n}\n\nconst model: NextModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: (params, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    }\n  },\n  // Provide for SSR\n  asyncState: async context => {\n    await waitFor(4000)\n    return { counter: 500 }\n  },\n  state: initialState\n}\n\nexport default Model(model)\n```\n\n</p>\n</details>\n\n<details>\n<summary>Global Config: _app.tsx</summary>\n<p>\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) => {\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    <Container>\n      <Component {...pageProps} />\n    </Container>\n  )\n}\n\nMyApp.getInitialProps = async (context: NextAppContext) => {\n  if (!(process as any).browser) {\n    const initialModels = context.Component.getInitialProps\n      ? await context.Component.getInitialProps(context.ctx)\n      await getInitialState() // get all model initialState\n      // : await getInitialState({ modelName: 'Home' }) // get Home initialState only\n      // : await getInitialState({ modelName: ['Home', 'Todo'] }) // get multi initialState\n      // : await getInitialState({ data }) // You can also pass some public data as asyncData params.\n    return { initialModels }\n  } else {\n    return { persistModel }\n  }\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Page: hooks/index.tsx</summary>\n<p>\n\n```tsx\nimport { useStore, getState } from '../index'\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button\n        onClick={e => {\n          actions.increment(33)\n        }}\n      >\n    </div>\n  )\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Single Page Config: benchmark.tsx</summary>\n<p>\n\n```tsx\n// ...\nBenchmark.getInitialProps = async () => {\n  return await getInitialState({ modelName: 'Todo' })\n}\n```\n</p>\n</details>\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<{}> = async (context, restMiddlewares) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: any) => 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) => async (params: any) => {\n  const context: Context = {\n    modelName,\n    setState,\n    actionName: action.name,\n    next: () => {},\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## 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      <Provider>\n        <Counter />\n      </Provider>\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<details>\n<summary>Javascript decorator version</summary>\n<p>\n\n```jsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\n\nconst mapProps = ({ light, counter }) => ({\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      <>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(5)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n      </>\n    )\n  }\n}\n```\n\n</p>\n</details>\n\n<details>\n<summary>TypeScript Version</summary>\n<p>\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) => ({\n  lightStatus: light ? 'open' : 'close',\n  counter,\n  response\n})\n\ntype RType = ReturnType<typeof mapProps>\n\nclass TSCounter extends PureComponent<\n  { state: RType } & { actions: ActionType }\n> {\n  render() {\n    const { state, actions } = this.props\n    return (\n      <>\n        <div>TS Counter</div>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(3)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n        <button onClick={e => actions.get()}>Get Response</button>\n        <div>message: {JSON.stringify(state.response)}</div>\n      </>\n    )\n  }\n}\n\nexport default connect(\n  'Home',\n  mapProps\n)(TSCounter)\n```\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\n\n## FAQ\n\n### How can I disable the console debugger\n\nJust remove consoleDebugger middleware.\n\n```typescript\nimport { actionMiddlewares } from 'react-model'\n// Find the index of middleware\nconst consoleDebuggerMiddlewareIndex = actionMiddlewares.indexOf(\n  middlewares.consoleDebugger\n)\n// Remove it\nactionMiddlewares.splice(consoleDebuggerMiddlewareIndex, 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) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: Error) => 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\nconst persistMiddleware: Middleware = async (context, restMiddlewares) => {\n  localStorage.setItem('__REACT_MODEL__', JSON.stringify(context.Global.State))\n  await context.next(restMiddlewares)\n}\n\nactionMiddlewares.push(persistMiddleware)\n\nModel({ Example }, JSON.parse(localStorage.getItem('__REACT_MODEL__')))\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      <div className=\"App\">\n        <Counter />\n        <Counter />\n        <Counter />\n      </div>\n    )\n  }\n}\n```\n\n<details>\n<summary>Counter model</summary>\n<p>\n\n```ts\ninterface State {\n  count: number\n}\n\ninterface ActionParams {\n  increment: number\n}\n\nconst model: NextModelType<State, ActionParams> = {\n  state: {\n    count: 0\n  },\n  actions: {\n    increment: payload => {\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 => (state.count += payload)\n      return state => {\n        state.count += payload\n      }\n    }\n  }\n}\n\n```\n\n</p>\n</details>\n\n<details>\n<summary>Counter.tsx</summary>\n<p>\n\n```tsx\n\nconst Counter = () => {\n  const [{ useStore }] = useState(() => Model(model))\n  const [state, actions] = useStore()\n  return (\n    <div>\n      <div>{state.count}</div>\n      <button onClick={() => actions.increment(3)}>Increment</button>\n    </div>\n  )\n}\n\nexport default Counter\n```\n\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\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 => {\n    // Not allowed\n    // return state => (state.count += payload)\n    return state => {\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\nIf you are using NextModel, you can customize each model's middlewares.\n\n```typescript\nimport { actionMiddlewares, Model } from 'react-model'\nimport { delayMiddleware } from './middlewares'\n\nconst nextCounterModel: NextModelType<CounterState, NextCounterActionParams> = {\n  actions: {\n    add: num => {\n      return state => {\n        state.count += num\n      }\n    },\n    increment: async (num, { actions }) => {\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","readmeFilename":"README.md","gitHead":"fbc40b4cec928b579acbfe722d78d61207fb739d","_id":"react-model@3.0.0-alpha.3","_npmVersion":"6.4.1","_nodeVersion":"8.12.0","_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"dist":{"integrity":"sha512-DuDbL7pr769XVE30zRmrSpFDBP9U9EZ7u1asOaJ5FYv7qwvntHUQ91zZal2Jm5uHXnTlZSCNdnT2yPf1A55V9Q==","shasum":"ac42570a5d982eda283779a08cc703119ebcb903","tarball":"https://registry.npmjs.org/react-model/-/react-model-3.0.0-alpha.3.tgz","fileCount":21,"unpackedSize":74129,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJc7REnCRA9TVsSAnZWagAAjKYQAKE54pcm8vOtLm0FVXLm\n2J6aFJrHMOz9JobDXMHy9ibuAoZyF4I8QWJ1OnSUoQN2HGO3hQcjb8gxsZEs\n2H60yH825JYaVaH78phG7D5BX6fpVYKro4ZoPbKeOkGr0OLjnhDPzzvsV/8Z\n7e4+Y6UH1qmTl8HbrUT5eTzRw58pWDfMtu0gm4TAbgNci4SVbHXmm5ywWut1\ngmC25O3vxfIwmBpVn62UCXsMFHSS/6kQGQdHJw5YnvKfLOaUPq68T7bSxMqX\nFYU07EU4fUMLr1XrJtRt0SSqSNG0Ed1zigKld5A/ojQ8s4iGU4PkBIKnnmI/\nL2QMcL190u0Clbng6sfZy87TybXQ13NlMUiCGnH3NmsyIAucKc0v7gp68+Xu\n8czW6fvQPz0VqW17mJ6iXrpHIPYeoawxnaWGbgHL5Rvf9D1Bme6L1mUwNg6H\neemJl0pgwpRxz9/jeT1kvcHqIvD2xBOxN7Lr2hGkmQvGM6jdqjEP9mMvfaoj\n2jVgzYneMk0oqQT5egQw/fyM6hyER5hkDZKfOaYC/N+flEx4IvGtbEJyFuwX\nSd98k/QgAPL5j7qRT8n6/gWH1362Hup7R5gMxP57ri0Vh9NhNeuAzd/5Cxrs\nBM5cyF05TnFrB8WZfMhvQzzbNHoOqDkHURux5MZi8qFXhEhBsSuBcKEgXxuQ\nAYyJ\r\n=Q3pc\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQCVKTIb3JQkoB0h7VnkuRnxYRbWC8IlANbDQtdGEUzL+QIhANTn4jmIhdaHRtcIDiKhUswICg9o2bNP+4reLW2pMWvw"}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_3.0.0-alpha.3_1559040294600_0.8522198351424586"},"_hasShrinkwrap":false},"3.0.0-alpha.4":{"name":"react-model","version":"3.0.0-alpha.4","description":"The State management library for React","main":"./dist/react-model.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^3.0.0"},"peerDependencies":{"react":"^16.3.0","react-dom":"^16.3.0"},"devDependencies":{"@commitlint/cli":"^8.0.0","@commitlint/config-conventional":"^8.0.0","@testing-library/react":"^8.0.1","@testing-library/react-hooks":"^1.0.4","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/jest":"^24.0.5","@types/node":"^12.0.0","@types/react":"^16.8.2","@types/react-dom":"^16.8.0","commitizen":"^3.0.7","cz-conventional-changelog":"^2.1.0","husky":"^2.0.0","jest":"^24.1.0","prettier":"^1.16.4","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^6.0.1","remark-lint":"^6.0.4","remark-preset-lint-recommended":"^3.0.2","ts-jest":"^24.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"gitHead":"4f681621adaaa8e5687a49f67ee07f238b08131f","readme":"# react-model &middot; ![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) [![Build Status](https://travis-ci.org/byte-fe/react-model.svg?branch=master)](https://travis-ci.org/byte-fe/react-model) [![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⚔ Fully TypeScript Support\n\n📦 built with microbundle\n\n⚙️ Middlewares Pipline ( redux-devtools support ... )\n\n☂️ 100% test coverage, safe on production\n\n🐛 Debug easily on test environment\n\n```tsx\nimport { Model } from 'react-model'\n\n// define model\nconst Todo = {\n  state: {\n    items: ['Install react-model', 'Read github docs', 'Build App']\n  },\n  actions: {\n    add: todo => {\n      // s is the readonly version of state\n      // you can also return partial state here but don't need to keep immutable manually\n      // state is the mutable state\n      return state => {\n        state.items.push(todo)\n      }\n    }\n  }\n}\n\n// Model Register\nconst { useStore } = Model(Todo)\n\nconst App = () => {\n  return <TodoList />\n}\n\nconst TodoList = () => {\n  const [state, actions] = useStore()\n  return <div>\n    <Addon handler={actions.add} />\n    {state.items.map((item, index) => (<Todo key={index} item={item} />))}\n  </div>\n}\n```\n\n---\n\n## Recently Updated\n\n* [Expand Context](#expand-context)\n* [ModelType Update: NextModelType => ModelType](#model)\n\n## Quick Start\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  - [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  - [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  - [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### Model\n\nEvery model have their 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: NextModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (payload, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    },\n    openLight: async (_, { state, actions }) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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(model)\n\n// You can use these types when use Class Components.\n// type ConsumerActionsType = getConsumerActionsType<typeof Model.actions>\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 keep the state and actions in the separate private store. 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 return from useStore can invoke the 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 () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button onClick={e => actions.increment(33)}>home increment</button>\n      <button onClick={e => sharedActions.increment(20)}>\n        shared increment\n      </button>\n      <button onClick={e => actions.get()}>fake request</button>\n      <button onClick={e => actions.openLight()}>fake nested call</button>\n    </div>\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 with depActions and React.memo to prevent child components rerender frequently.](https://github.com/ArrayZoneYour/react-model-todomvc/blob/master/src/components/TodoItem.tsx)\n\n[Demo Repo](https://github.com/ArrayZoneYour/react-model-todomvc)\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> Hint: The state returned should only be used as readonly\n\n```jsx\nimport { useStore, getState } from '../model/index'\n\nconst BasicHook = () => {\n  const [state, actions] = useStore('Counter')\n  useEffect(() => {\n    console.log('some mounted actions from BasicHooks')\n    return () =>\n      console.log(\n        `Basic Hooks unmounted, current Counter state: ${JSON.stringify(\n          getState('Counter')\n        )}`\n      )\n  }, [])\n  return (\n    <>\n      <div>state: {JSON.stringify(state)}</div>\n    </>\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: () => {\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 = () => {\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: NextModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (s, _, params) => {\n      // issue: https://github.com/Microsoft/TypeScript/issues/29196\n      // async function return produce need define type manually.\n      return (state: typeof s) => {\n        state.counter += params || 1\n      }\n    },\n    decrease: (s, _, params) => s => {\n      s.counter += params || 1\n    }\n  }\n}\n\nexport default Model(model)\n```\n\nJavaScript Example\n\n```js\nconst Model = {\n  actions: {\n    increment: async (s, _, params) => {\n      return state => {\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<details>\n<summary>Store: shared.ts</summary>\n<p>\n\n```ts\nconst initialState = {\n  counter: 0\n}\n\nconst model: NextModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: (params, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    }\n  },\n  // Provide for SSR\n  asyncState: async context => {\n    await waitFor(4000)\n    return { counter: 500 }\n  },\n  state: initialState\n}\n\nexport default Model(model)\n```\n\n</p>\n</details>\n\n<details>\n<summary>Global Config: _app.tsx</summary>\n<p>\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) => {\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    <Container>\n      <Component {...pageProps} />\n    </Container>\n  )\n}\n\nMyApp.getInitialProps = async (context: NextAppContext) => {\n  if (!(process as any).browser) {\n    const initialModels = context.Component.getInitialProps\n      ? await context.Component.getInitialProps(context.ctx)\n      await getInitialState() // get all model initialState\n      // : await getInitialState({ modelName: 'Home' }) // get Home initialState only\n      // : await getInitialState({ modelName: ['Home', 'Todo'] }) // get multi initialState\n      // : await getInitialState({ data }) // You can also pass some public data as asyncData params.\n    return { initialModels }\n  } else {\n    return { persistModel }\n  }\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Page: hooks/index.tsx</summary>\n<p>\n\n```tsx\nimport { useStore, getState } from '../index'\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button\n        onClick={e => {\n          actions.increment(33)\n        }}\n      >\n    </div>\n  )\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Single Page Config: benchmark.tsx</summary>\n<p>\n\n```tsx\n// ...\nBenchmark.getInitialProps = async () => {\n  return await getInitialState({ modelName: 'Todo' })\n}\n```\n</p>\n</details>\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<{}> = async (context, restMiddlewares) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: any) => 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) => async (params: any) => {\n  const context: Context = {\n    modelName,\n    setState,\n    actionName: action.name,\n    next: () => {},\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<\n  { name: string }, // State Type\n  { ext: undefined }, // ActionParamsType\n  { name: string } // ExtContextType\n> = {\n  actions: {\n    // { state, action } => { state, action, [name] }\n    ext: (_, { name }) => {\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 => '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      <Provider>\n        <Counter />\n      </Provider>\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<details>\n<summary>Javascript decorator version</summary>\n<p>\n\n```jsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\n\nconst mapProps = ({ light, counter }) => ({\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      <>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(5)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n      </>\n    )\n  }\n}\n```\n\n</p>\n</details>\n\n<details>\n<summary>TypeScript Version</summary>\n<p>\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) => ({\n  lightStatus: light ? 'open' : 'close',\n  counter,\n  response\n})\n\ntype RType = ReturnType<typeof mapProps>\n\nclass TSCounter extends PureComponent<\n  { state: RType } & { actions: ActionType }\n> {\n  render() {\n    const { state, actions } = this.props\n    return (\n      <>\n        <div>TS Counter</div>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(3)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n        <button onClick={e => actions.get()}>Get Response</button>\n        <div>message: {JSON.stringify(state.response)}</div>\n      </>\n    )\n  }\n}\n\nexport default connect(\n  'Home',\n  mapProps\n)(TSCounter)\n```\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\n\n## FAQ\n\n### How can I disable the console debugger\n\nJust remove consoleDebugger middleware.\n\n```typescript\nimport { actionMiddlewares } from 'react-model'\n// Find the index of middleware\nconst consoleDebuggerMiddlewareIndex = actionMiddlewares.indexOf(\n  middlewares.consoleDebugger\n)\n// Remove it\nactionMiddlewares.splice(consoleDebuggerMiddlewareIndex, 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) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: Error) => 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\nconst persistMiddleware: Middleware = async (context, restMiddlewares) => {\n  localStorage.setItem('__REACT_MODEL__', JSON.stringify(context.Global.State))\n  await context.next(restMiddlewares)\n}\n\nactionMiddlewares.push(persistMiddleware)\n\nModel({ Example }, JSON.parse(localStorage.getItem('__REACT_MODEL__')))\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      <div className=\"App\">\n        <Counter />\n        <Counter />\n        <Counter />\n      </div>\n    )\n  }\n}\n```\n\n<details>\n<summary>Counter model</summary>\n<p>\n\n```ts\ninterface State {\n  count: number\n}\n\ninterface ActionParams {\n  increment: number\n}\n\nconst model: NextModelType<State, ActionParams> = {\n  state: {\n    count: 0\n  },\n  actions: {\n    increment: payload => {\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 => (state.count += payload)\n      return state => {\n        state.count += payload\n      }\n    }\n  }\n}\n\n```\n\n</p>\n</details>\n\n<details>\n<summary>Counter.tsx</summary>\n<p>\n\n```tsx\n\nconst Counter = () => {\n  const [{ useStore }] = useState(() => Model(model))\n  const [state, actions] = useStore()\n  return (\n    <div>\n      <div>{state.count}</div>\n      <button onClick={() => actions.increment(3)}>Increment</button>\n    </div>\n  )\n}\n\nexport default Counter\n```\n\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\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 => {\n    // Not allowed\n    // return state => (state.count += payload)\n    return state => {\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\nIf you are using NextModel, you can customize each model's middlewares.\n\n```typescript\nimport { actionMiddlewares, Model } from 'react-model'\nimport { delayMiddleware } from './middlewares'\n\nconst nextCounterModel: NextModelType<CounterState, NextCounterActionParams> = {\n  actions: {\n    add: num => {\n      return state => {\n        state.count += num\n      }\n    },\n    increment: async (num, { actions }) => {\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","readmeFilename":"README.md","_id":"react-model@3.0.0-alpha.4","_nodeVersion":"11.11.0","_npmVersion":"6.9.0","dist":{"integrity":"sha512-FOM7zOkc5zO0Pe1DxAKvAfC81j3mY+47fUAKbq1WzRG19bYJp/YORbhlEwcUu88amFn41+XL+DQWL4EatRRCbQ==","shasum":"6b9c4250bca51d7dd82dd98f234ff560db9bb5de","tarball":"https://registry.npmjs.org/react-model/-/react-model-3.0.0-alpha.4.tgz","fileCount":21,"unpackedSize":74739,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJdEcbsCRA9TVsSAnZWagAAh3UP/AkOLU+Ep+3S1t2P2rDX\n8kcY+dQXMmAdpVHyJzzmL+Yxi66+zZ3M0E//1LOJdCG+zxTcsBa5on2aJDHF\nXeXlKXNBcFHDo8BgBPMeRSffbC8am+5ew+ur6WKJbZVsotyHC/UQPn6vBDXQ\nlwDu61tJe3qjBVObfVoepfv/0BxvVVR8+DjJ3yONM6T7u817XjlRk9TogfZI\nXIYFZTjlpS4gqZf8UaDsw8RAye2L17HLkLjrfmV6QTERHVoSeRkINV0NmP94\nzS3P8vsnhOTzVGav5JYYK9P04C5ZyHid5iLgQCrf2pjPney4GMHXeH9n2F4l\nxwd/cEplC32eI0GQee/hTBqLNN5ejz9fKmlV2BSyfNvoT91hTJCknVsiChp2\nWVCJ9CGefxmPl8B+0etRlooIJuhlQpcE8C0h+iAF61AEPTDheHp+cN5nq6u8\nOMXd142itbm2dy0nC0HfWqfd59V0HL5pFAcjXZNlsa+Tht0VA1TW929eO67Q\n5bk3fvc4Udcq6udiGAVy4qaHzQCfzL9Nk1L3eb8nLcIKQnLXhYUpGvAy8o+/\nZEoiGFfYkD4A7Ky+UkTE4QCkms84Svw3P7uGcrIX92RL09WrLogRcJdNRs6A\nMkdySJSAUK10/xMKftWQF0qjBpOvA+zXDiJpLmTHwGgTHc0FdMUMJyJkN4ND\n4nTy\r\n=QpxZ\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIF0HobtvyGIHsHQJBaCckbSv8k08Isz3GIgarRyjSbrBAiEA/QfgGo+HSWAnNal9N1cZjuZVPaZGOEuoJJ4dnCz655k="}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_3.0.0-alpha.4_1561446123355_0.815190802040465"},"_hasShrinkwrap":false},"3.0.0-rc":{"name":"react-model","version":"3.0.0-rc","description":"The State management library for React","main":"./dist/react-model.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^3.0.0"},"peerDependencies":{"react":"^16.3.0","react-dom":"^16.3.0"},"devDependencies":{"@commitlint/cli":"^8.0.0","@commitlint/config-conventional":"^8.0.0","@testing-library/react":"^8.0.1","@testing-library/react-hooks":"^1.0.4","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/jest":"^24.0.5","@types/node":"^12.0.0","@types/react":"^16.8.2","@types/react-dom":"^16.8.0","commitizen":"^3.0.7","cz-conventional-changelog":"^2.1.0","husky":"^2.0.0","jest":"^24.1.0","prettier":"^1.16.4","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^6.0.1","remark-lint":"^6.0.4","remark-preset-lint-recommended":"^3.0.2","ts-jest":"^24.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"readme":"# react-model &middot; ![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) [![Build Status](https://travis-ci.org/byte-fe/react-model.svg?branch=master)](https://travis-ci.org/byte-fe/react-model) [![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⚔ Fully TypeScript Support\n\n📦 built with microbundle\n\n⚙️ Middlewares Pipline ( redux-devtools support ... )\n\n☂️ 100% test coverage, safe on production\n\n🐛 Debug easily on test environment\n\n```tsx\nimport { Model } from 'react-model'\n\n// define model\nconst Todo = {\n  state: {\n    items: ['Install react-model', 'Read github docs', 'Build App']\n  },\n  actions: {\n    add: todo => {\n      // s is the readonly version of state\n      // you can also return partial state here but don't need to keep immutable manually\n      // state is the mutable state\n      return state => {\n        state.items.push(todo)\n      }\n    }\n  }\n}\n\n// Model Register\nconst { useStore } = Model(Todo)\n\nconst App = () => {\n  return <TodoList />\n}\n\nconst TodoList = () => {\n  const [state, actions] = useStore()\n  return <div>\n    <Addon handler={actions.add} />\n    {state.items.map((item, index) => (<Todo key={index} item={item} />))}\n  </div>\n}\n```\n\n---\n\n## Recently Updated\n\n* [Expand Context](#expand-context)\n* [ModelType Update: NextModelType => ModelType](#model)\n\n## Quick Start\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  - [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  - [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  - [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### Model\n\nEvery model have their 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: NextModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (payload, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    },\n    openLight: async (_, { state, actions }) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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(model)\n\n// You can use these types when use Class Components.\n// type ConsumerActionsType = getConsumerActionsType<typeof Model.actions>\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 keep the state and actions in the separate private store. 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 return from useStore can invoke the 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 () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button onClick={e => actions.increment(33)}>home increment</button>\n      <button onClick={e => sharedActions.increment(20)}>\n        shared increment\n      </button>\n      <button onClick={e => actions.get()}>fake request</button>\n      <button onClick={e => actions.openLight()}>fake nested call</button>\n    </div>\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 with depActions and React.memo to prevent child components rerender frequently.](https://github.com/ArrayZoneYour/react-model-todomvc/blob/master/src/components/TodoItem.tsx)\n\n[Demo Repo](https://github.com/ArrayZoneYour/react-model-todomvc)\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> Hint: The state returned should only be used as readonly\n\n```jsx\nimport { useStore, getState } from '../model/index'\n\nconst BasicHook = () => {\n  const [state, actions] = useStore('Counter')\n  useEffect(() => {\n    console.log('some mounted actions from BasicHooks')\n    return () =>\n      console.log(\n        `Basic Hooks unmounted, current Counter state: ${JSON.stringify(\n          getState('Counter')\n        )}`\n      )\n  }, [])\n  return (\n    <>\n      <div>state: {JSON.stringify(state)}</div>\n    </>\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: () => {\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 = () => {\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: NextModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (s, _, params) => {\n      // issue: https://github.com/Microsoft/TypeScript/issues/29196\n      // async function return produce need define type manually.\n      return (state: typeof s) => {\n        state.counter += params || 1\n      }\n    },\n    decrease: (s, _, params) => s => {\n      s.counter += params || 1\n    }\n  }\n}\n\nexport default Model(model)\n```\n\nJavaScript Example\n\n```js\nconst Model = {\n  actions: {\n    increment: async (s, _, params) => {\n      return state => {\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<details>\n<summary>Store: shared.ts</summary>\n<p>\n\n```ts\nconst initialState = {\n  counter: 0\n}\n\nconst model: NextModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: (params, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    }\n  },\n  // Provide for SSR\n  asyncState: async context => {\n    await waitFor(4000)\n    return { counter: 500 }\n  },\n  state: initialState\n}\n\nexport default Model(model)\n```\n\n</p>\n</details>\n\n<details>\n<summary>Global Config: _app.tsx</summary>\n<p>\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) => {\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    <Container>\n      <Component {...pageProps} />\n    </Container>\n  )\n}\n\nMyApp.getInitialProps = async (context: NextAppContext) => {\n  if (!(process as any).browser) {\n    const initialModels = context.Component.getInitialProps\n      ? await context.Component.getInitialProps(context.ctx)\n      await getInitialState() // get all model initialState\n      // : await getInitialState({ modelName: 'Home' }) // get Home initialState only\n      // : await getInitialState({ modelName: ['Home', 'Todo'] }) // get multi initialState\n      // : await getInitialState({ data }) // You can also pass some public data as asyncData params.\n    return { initialModels }\n  } else {\n    return { persistModel }\n  }\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Page: hooks/index.tsx</summary>\n<p>\n\n```tsx\nimport { useStore, getState } from '../index'\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button\n        onClick={e => {\n          actions.increment(33)\n        }}\n      >\n    </div>\n  )\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Single Page Config: benchmark.tsx</summary>\n<p>\n\n```tsx\n// ...\nBenchmark.getInitialProps = async () => {\n  return await getInitialState({ modelName: 'Todo' })\n}\n```\n</p>\n</details>\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<{}> = async (context, restMiddlewares) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: any) => 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) => async (params: any) => {\n  const context: Context = {\n    modelName,\n    setState,\n    actionName: action.name,\n    next: () => {},\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<\n  { name: string }, // State Type\n  { ext: undefined }, // ActionParamsType\n  { name: string } // ExtContextType\n> = {\n  actions: {\n    // { state, action } => { state, action, [name] }\n    ext: (_, { name }) => {\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 => '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      <Provider>\n        <Counter />\n      </Provider>\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<details>\n<summary>Javascript decorator version</summary>\n<p>\n\n```jsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\n\nconst mapProps = ({ light, counter }) => ({\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      <>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(5)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n      </>\n    )\n  }\n}\n```\n\n</p>\n</details>\n\n<details>\n<summary>TypeScript Version</summary>\n<p>\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) => ({\n  lightStatus: light ? 'open' : 'close',\n  counter,\n  response\n})\n\ntype RType = ReturnType<typeof mapProps>\n\nclass TSCounter extends PureComponent<\n  { state: RType } & { actions: ActionType }\n> {\n  render() {\n    const { state, actions } = this.props\n    return (\n      <>\n        <div>TS Counter</div>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(3)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n        <button onClick={e => actions.get()}>Get Response</button>\n        <div>message: {JSON.stringify(state.response)}</div>\n      </>\n    )\n  }\n}\n\nexport default connect(\n  'Home',\n  mapProps\n)(TSCounter)\n```\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\n\n## FAQ\n\n### How can I disable the console debugger\n\nJust remove consoleDebugger middleware.\n\n```typescript\nimport { actionMiddlewares } from 'react-model'\n// Find the index of middleware\nconst consoleDebuggerMiddlewareIndex = actionMiddlewares.indexOf(\n  middlewares.consoleDebugger\n)\n// Remove it\nactionMiddlewares.splice(consoleDebuggerMiddlewareIndex, 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) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: Error) => 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\nconst persistMiddleware: Middleware = async (context, restMiddlewares) => {\n  localStorage.setItem('__REACT_MODEL__', JSON.stringify(context.Global.State))\n  await context.next(restMiddlewares)\n}\n\nactionMiddlewares.push(persistMiddleware)\n\nModel({ Example }, JSON.parse(localStorage.getItem('__REACT_MODEL__')))\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      <div className=\"App\">\n        <Counter />\n        <Counter />\n        <Counter />\n      </div>\n    )\n  }\n}\n```\n\n<details>\n<summary>Counter model</summary>\n<p>\n\n```ts\ninterface State {\n  count: number\n}\n\ninterface ActionParams {\n  increment: number\n}\n\nconst model: NextModelType<State, ActionParams> = {\n  state: {\n    count: 0\n  },\n  actions: {\n    increment: payload => {\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 => (state.count += payload)\n      return state => {\n        state.count += payload\n      }\n    }\n  }\n}\n\n```\n\n</p>\n</details>\n\n<details>\n<summary>Counter.tsx</summary>\n<p>\n\n```tsx\n\nconst Counter = () => {\n  const [{ useStore }] = useState(() => Model(model))\n  const [state, actions] = useStore()\n  return (\n    <div>\n      <div>{state.count}</div>\n      <button onClick={() => actions.increment(3)}>Increment</button>\n    </div>\n  )\n}\n\nexport default Counter\n```\n\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\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 => {\n    // Not allowed\n    // return state => (state.count += payload)\n    return state => {\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\nIf you are using NextModel, you can customize each model's middlewares.\n\n```typescript\nimport { actionMiddlewares, Model } from 'react-model'\nimport { delayMiddleware } from './middlewares'\n\nconst nextCounterModel: NextModelType<CounterState, NextCounterActionParams> = {\n  actions: {\n    add: num => {\n      return state => {\n        state.count += num\n      }\n    },\n    increment: async (num, { actions }) => {\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","readmeFilename":"README.md","gitHead":"9ec44af8fc502d02183747dd180798762227d60b","_id":"react-model@3.0.0-rc","_nodeVersion":"12.10.0","_npmVersion":"6.10.3","dist":{"integrity":"sha512-PT15xkxSf98ryxZEewJfjhsYJF+3Ilyq0/cdS7Ee+0Ljs945LtDoAM1pIIToe8VGP8CSon98osp85FE01XszzA==","shasum":"0fe0827c7fb94e6f4ebbdd4099c826dab2113bc7","tarball":"https://registry.npmjs.org/react-model/-/react-model-3.0.0-rc.tgz","fileCount":24,"unpackedSize":74997,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJdcLzDCRA9TVsSAnZWagAAPokP/3EKbSJXXVtnPidm/Kl3\nLUYBuZUOov5+276sc/zh3IZ+oD5ask0qypkPjsthBtDDveV8n8yFr029ptzU\nFCUwTQZwqyqdOc+6z7P2sU5yWTCTj51CeyUrEqE/aGL1FCbqZPDGJaH/AZG+\nM31pFqaDrP1w4cHpYiWrIu9amy7UpH3zOPOYMVM6xCzTPjUFA2zVMnnTC2m7\njglZywd1aLPHAAkGhD0ylCqpY6EgqjNAbISFFlhVopvdrUCXqtK28mw7T4Rp\nuPyMj2lhWXUQ8mLUXnvss65cqko7TNqi+QEmUv9v3FaKeHzPC7XAugc5c2sh\nIXwxq1075VJ/CR0X5rdN8Pt8jt86282bRA64oHUPo/kTK0aPV6PpcgJk6LLH\np3P7EV+MWHYk0uWmseKvU04cmze5ZG+BtsmoPaCf5msrjRiElLuxbVmH6Nfl\nruHqvXnwemBkZN8XBN6rS4QQXcgJg5bc9oUBG3LcbS7OSzwxTs5rSXgej9AE\n7AljjsPu7YkXagJrB/o1SwmTq3nswXjLx8eY1mFJhwHiKsno3NzrlVeuPfEZ\nxBik5LUlmrJwie4SBLDRp92niCH+JwqfAFiJ4he/CphGbra4jBLAO6R6Qlvn\nO/varr1w3jRY+vOrFiQo0eq50VU/EJSpKKJERRAWCUPbPQETiygRErGa+/rZ\n1uen\r\n=vzg6\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDIY5C88SITOZkW4MygSbW03WBSQj1mH/jjpS35ipZ1MgIhAN/MmejwKmzVeN8QjrPC3iTsHy1AW3dccWxoeCykUf8O"}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_3.0.0-rc_1567669443267_0.8525636914511192"},"_hasShrinkwrap":false},"3.0.0-rc.0":{"name":"react-model","version":"3.0.0-rc.0","description":"The State management library for React","main":"./dist/react-model.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^3.0.0"},"peerDependencies":{"react":"^16.3.0","react-dom":"^16.3.0"},"devDependencies":{"@commitlint/cli":"^8.0.0","@commitlint/config-conventional":"^8.0.0","@testing-library/react":"^8.0.1","@testing-library/react-hooks":"^1.0.4","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/jest":"^24.0.5","@types/node":"^12.0.0","@types/react":"^16.8.2","@types/react-dom":"^16.8.0","commitizen":"^3.0.7","cz-conventional-changelog":"^2.1.0","husky":"^2.0.0","jest":"^24.1.0","prettier":"^1.16.4","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^6.0.1","remark-lint":"^6.0.4","remark-preset-lint-recommended":"^3.0.2","ts-jest":"^24.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"readme":"# react-model &middot; ![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) [![Build Status](https://travis-ci.org/byte-fe/react-model.svg?branch=master)](https://travis-ci.org/byte-fe/react-model) [![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⚔ Fully TypeScript Support\n\n📦 built with microbundle\n\n⚙️ Middlewares Pipline ( redux-devtools support ... )\n\n☂️ 100% test coverage, safe on production\n\n🐛 Debug easily on test environment\n\n```tsx\nimport { Model } from 'react-model'\n\n// define model\nconst Todo = {\n  state: {\n    items: ['Install react-model', 'Read github docs', 'Build App']\n  },\n  actions: {\n    add: todo => {\n      // s is the readonly version of state\n      // you can also return partial state here but don't need to keep immutable manually\n      // state is the mutable state\n      return state => {\n        state.items.push(todo)\n      }\n    }\n  }\n}\n\n// Model Register\nconst { useStore } = Model(Todo)\n\nconst App = () => {\n  return <TodoList />\n}\n\nconst TodoList = () => {\n  const [state, actions] = useStore()\n  return <div>\n    <Addon handler={actions.add} />\n    {state.items.map((item, index) => (<Todo key={index} item={item} />))}\n  </div>\n}\n```\n\n---\n\n## Recently Updated\n\n* [Expand Context](#expand-context)\n* [ModelType Update: NextModelType => ModelType](#model)\n\n## Quick Start\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  - [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  - [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  - [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### Model\n\nEvery model have their 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: NextModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (payload, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    },\n    openLight: async (_, { state, actions }) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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(model)\n\n// You can use these types when use Class Components.\n// type ConsumerActionsType = getConsumerActionsType<typeof Model.actions>\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 keep the state and actions in the separate private store. 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 return from useStore can invoke the 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 () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button onClick={e => actions.increment(33)}>home increment</button>\n      <button onClick={e => sharedActions.increment(20)}>\n        shared increment\n      </button>\n      <button onClick={e => actions.get()}>fake request</button>\n      <button onClick={e => actions.openLight()}>fake nested call</button>\n    </div>\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 with depActions and React.memo to prevent child components rerender frequently.](https://github.com/ArrayZoneYour/react-model-todomvc/blob/master/src/components/TodoItem.tsx)\n\n[Demo Repo](https://github.com/ArrayZoneYour/react-model-todomvc)\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> Hint: The state returned should only be used as readonly\n\n```jsx\nimport { useStore, getState } from '../model/index'\n\nconst BasicHook = () => {\n  const [state, actions] = useStore('Counter')\n  useEffect(() => {\n    console.log('some mounted actions from BasicHooks')\n    return () =>\n      console.log(\n        `Basic Hooks unmounted, current Counter state: ${JSON.stringify(\n          getState('Counter')\n        )}`\n      )\n  }, [])\n  return (\n    <>\n      <div>state: {JSON.stringify(state)}</div>\n    </>\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: () => {\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 = () => {\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: NextModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (s, _, params) => {\n      // issue: https://github.com/Microsoft/TypeScript/issues/29196\n      // async function return produce need define type manually.\n      return (state: typeof s) => {\n        state.counter += params || 1\n      }\n    },\n    decrease: (s, _, params) => s => {\n      s.counter += params || 1\n    }\n  }\n}\n\nexport default Model(model)\n```\n\nJavaScript Example\n\n```js\nconst Model = {\n  actions: {\n    increment: async (s, _, params) => {\n      return state => {\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<details>\n<summary>Store: shared.ts</summary>\n<p>\n\n```ts\nconst initialState = {\n  counter: 0\n}\n\nconst model: NextModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: (params, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    }\n  },\n  // Provide for SSR\n  asyncState: async context => {\n    await waitFor(4000)\n    return { counter: 500 }\n  },\n  state: initialState\n}\n\nexport default Model(model)\n```\n\n</p>\n</details>\n\n<details>\n<summary>Global Config: _app.tsx</summary>\n<p>\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) => {\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    <Container>\n      <Component {...pageProps} />\n    </Container>\n  )\n}\n\nMyApp.getInitialProps = async (context: NextAppContext) => {\n  if (!(process as any).browser) {\n    const initialModels = context.Component.getInitialProps\n      ? await context.Component.getInitialProps(context.ctx)\n      await getInitialState() // get all model initialState\n      // : await getInitialState({ modelName: 'Home' }) // get Home initialState only\n      // : await getInitialState({ modelName: ['Home', 'Todo'] }) // get multi initialState\n      // : await getInitialState({ data }) // You can also pass some public data as asyncData params.\n    return { initialModels }\n  } else {\n    return { persistModel }\n  }\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Page: hooks/index.tsx</summary>\n<p>\n\n```tsx\nimport { useStore, getState } from '../index'\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button\n        onClick={e => {\n          actions.increment(33)\n        }}\n      >\n    </div>\n  )\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Single Page Config: benchmark.tsx</summary>\n<p>\n\n```tsx\n// ...\nBenchmark.getInitialProps = async () => {\n  return await getInitialState({ modelName: 'Todo' })\n}\n```\n</p>\n</details>\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<{}> = async (context, restMiddlewares) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: any) => 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) => async (params: any) => {\n  const context: Context = {\n    modelName,\n    setState,\n    actionName: action.name,\n    next: () => {},\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<\n  { name: string }, // State Type\n  { ext: undefined }, // ActionParamsType\n  { name: string } // ExtContextType\n> = {\n  actions: {\n    // { state, action } => { state, action, [name] }\n    ext: (_, { name }) => {\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 => '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      <Provider>\n        <Counter />\n      </Provider>\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<details>\n<summary>Javascript decorator version</summary>\n<p>\n\n```jsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\n\nconst mapProps = ({ light, counter }) => ({\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      <>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(5)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n      </>\n    )\n  }\n}\n```\n\n</p>\n</details>\n\n<details>\n<summary>TypeScript Version</summary>\n<p>\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) => ({\n  lightStatus: light ? 'open' : 'close',\n  counter,\n  response\n})\n\ntype RType = ReturnType<typeof mapProps>\n\nclass TSCounter extends PureComponent<\n  { state: RType } & { actions: ActionType }\n> {\n  render() {\n    const { state, actions } = this.props\n    return (\n      <>\n        <div>TS Counter</div>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(3)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n        <button onClick={e => actions.get()}>Get Response</button>\n        <div>message: {JSON.stringify(state.response)}</div>\n      </>\n    )\n  }\n}\n\nexport default connect(\n  'Home',\n  mapProps\n)(TSCounter)\n```\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\n\n## FAQ\n\n### How can I disable the console debugger\n\nJust remove consoleDebugger middleware.\n\n```typescript\nimport { actionMiddlewares } from 'react-model'\n// Find the index of middleware\nconst consoleDebuggerMiddlewareIndex = actionMiddlewares.indexOf(\n  middlewares.consoleDebugger\n)\n// Remove it\nactionMiddlewares.splice(consoleDebuggerMiddlewareIndex, 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) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: Error) => 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\nconst persistMiddleware: Middleware = async (context, restMiddlewares) => {\n  localStorage.setItem('__REACT_MODEL__', JSON.stringify(context.Global.State))\n  await context.next(restMiddlewares)\n}\n\nactionMiddlewares.push(persistMiddleware)\n\nModel({ Example }, JSON.parse(localStorage.getItem('__REACT_MODEL__')))\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      <div className=\"App\">\n        <Counter />\n        <Counter />\n        <Counter />\n      </div>\n    )\n  }\n}\n```\n\n<details>\n<summary>Counter model</summary>\n<p>\n\n```ts\ninterface State {\n  count: number\n}\n\ninterface ActionParams {\n  increment: number\n}\n\nconst model: NextModelType<State, ActionParams> = {\n  state: {\n    count: 0\n  },\n  actions: {\n    increment: payload => {\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 => (state.count += payload)\n      return state => {\n        state.count += payload\n      }\n    }\n  }\n}\n\n```\n\n</p>\n</details>\n\n<details>\n<summary>Counter.tsx</summary>\n<p>\n\n```tsx\n\nconst Counter = () => {\n  const [{ useStore }] = useState(() => Model(model))\n  const [state, actions] = useStore()\n  return (\n    <div>\n      <div>{state.count}</div>\n      <button onClick={() => actions.increment(3)}>Increment</button>\n    </div>\n  )\n}\n\nexport default Counter\n```\n\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\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 => {\n    // Not allowed\n    // return state => (state.count += payload)\n    return state => {\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\nIf you are using NextModel, you can customize each model's middlewares.\n\n```typescript\nimport { actionMiddlewares, Model } from 'react-model'\nimport { delayMiddleware } from './middlewares'\n\nconst nextCounterModel: NextModelType<CounterState, NextCounterActionParams> = {\n  actions: {\n    add: num => {\n      return state => {\n        state.count += num\n      }\n    },\n    increment: async (num, { actions }) => {\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","readmeFilename":"README.md","gitHead":"9ec44af8fc502d02183747dd180798762227d60b","_id":"react-model@3.0.0-rc.0","_nodeVersion":"12.10.0","_npmVersion":"6.10.3","dist":{"integrity":"sha512-RMEScEAa1fYBPV69ClrCiZn6NGQbefZdj/tmWIdtNHGo4RF7G21/L6cPP7iM6sCy7WMwSV3DGm/u8fTxKzd9YA==","shasum":"26f04f1991c431a6fabbf4f52e7f804d840bb5f3","tarball":"https://registry.npmjs.org/react-model/-/react-model-3.0.0-rc.0.tgz","fileCount":24,"unpackedSize":76794,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJdcL4bCRA9TVsSAnZWagAAproQAIqGeWktriYqWN7vByAt\nQnHEMLXKSwFYtx8+cS+7s/uEcuzmzzJdQQY7Ma54H8m+sR6JMhJpscrZXv/I\nCV8844UfJo+5Lt8BYM1In2ARX/Hl0wFE/Ezw+tx/XNz9r3vFtvpci4lK6J8M\nDZcrCIMqTSs4034pQGvJ3kBxBbULh34RTq0OfORb6t8MZ2LM5mDKrLsal/xG\nsDmz7ZkWUUrjWdU/0DPFHjB/XDmFDNMjv9jJPJ3AJoxOgkkST+qe7LNL6VzF\nte0SUOq5sGvIAz+kzXy4ui6LTT3coKu/wgxcFUhiVOF7X3HA5tfiOToKE3AT\nPrT0HHDmgq6X69tzDDgXuskxEPJdBYoWg+y+GBxVClZcL8nWPp1nwEDb3r0K\n+bpgC/juPLErL9jv8/MNU/VdnhTD1pt5l0jxG7K9Cd5/oAsjsD+KwOYui9Vm\n/CxwwVRI+RGCReGLg6ILqGgZvRzrEHXT16bxIta9zKo9bb5QNXIAZqGJj6mm\nVAul6rZO2CtbjgDO2P8xIBOVGT6g6Ji563+9ymC+SEi+HsjsAHJ8GFdyXt/F\nM8WxoTPeVPRgTmvHE1ZetPO7etScALBwS137k3DmzLv0gB4Y98P2nfEl0h6q\nkxfsYk2aZ97x4ryDpNLb6ke8PDz75W+4uFRuGP5T6RcWPhsl5yuNwzM1Awsx\n4mMk\r\n=Ubi9\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDs5g9VCviRzCeyT914C3uT6eMqNFNySJBR4D2g8495fwIgTtbeX1EHdKmqbGBVEHAnfY/IsM9yFRzsJpAzc8bF/hs="}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_3.0.0-rc.0_1567669787053_0.9467067562129268"},"_hasShrinkwrap":false},"3.0.0":{"name":"react-model","version":"3.0.0","description":"The State management library for React","main":"./dist/react-model.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^3.0.0"},"peerDependencies":{"react":"^16.3.0","react-dom":"^16.3.0"},"devDependencies":{"@commitlint/cli":"^8.1.0","@commitlint/config-conventional":"^8.1.0","@testing-library/react":"^9.1.3","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/jest":"^24.0.5","@types/node":"^12.7.4","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","husky":"^3.0.0","jest":"^24.1.0","prettier":"^1.16.4","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^7.0.0","remark-lint":"^6.0.4","remark-preset-lint-recommended":"^3.0.2","ts-jest":"^24.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"gitHead":"aa33c1ac544f8c98e2ae1255f7d292c22cd9ed24","_id":"react-model@3.0.0","_nodeVersion":"12.10.0","_npmVersion":"6.10.3","dist":{"integrity":"sha512-tX7qYAz3U+lsjCf3G6JzP3ZW4LjaPpO8BvBv9Cwua7BDhXymyniIEd8u77OuOzZ3qwn2G0Ec5KJ4fbkeX9HgoQ==","shasum":"7a4d7905b01bee1840b97782d127e3ce31e17e9b","tarball":"https://registry.npmjs.org/react-model/-/react-model-3.0.0.tgz","fileCount":24,"unpackedSize":76551,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJdcNXZCRA9TVsSAnZWagAAkmUP/RQAocpBE0OdiHhw/0CW\nUMGHdhZwBgOM0UgKdn54czsz0d4Y6bTnMao0azydg3H65pUl7Z0FW6sZjXLa\nUejoeA6GT2U5r1042k3JKShL4x6u5yqfnzqF5Gim3kCY/FQYDWVXB7XXqUQr\neJafcQyXVR7YHUeaoMxcBknaz9n1JnwbUSFGNLbpUKtWioe20nDVlAzGLvDo\nj7F/XRct4gfN8Tarx0Uw4rMrbANa2EWuXB8UDXrzZOzebgSOsrq3ms7Qedid\n+lTqCnLbD7khKMDrti0ljWj7cJi0I5wbnJEpTE190hWiQo/t1Em/r912D4m3\noMAoVQtn8w5KTOdkVT1fZ8xCGow8keJMkpTN+fVa3HqhIFFJuOEkQey8mYck\nEOakbRXhJrA+e5C7E46/nRnFsFMP8Fvx/k+emGW0LHChNebTpdzlbsXvK5Tm\n/riHzkukpqrQtLheY5sB+S3SCPRpcE8bjt8Xx2N11A3DijYeTQ60PqeklmkN\nIzHw2R5GGrxTJBJgmlcY47I4mPN8Q/p3ztknHna2BLznUfOMq/wL2g7C3Kx1\nbtVm5vxm+3EaHZRLuH+mm14GCf+MPQ5hj4/R1vHSiSLHDRpmMAkOFLgi0cZ7\nJluf9tROwxmKDAxTFjLMcSwhi5py048/EZY1ZCXS6u3Sh77zhjFV4/H5rwBn\nQ3Ot\r\n=715v\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCICJP6F61Lf+3tYrc2IsB96ep/8GCy/9ZTuueN01AtXujAiBekJbsGMacgZsnS+apP2Cmkq3uRWDBKryTvSEJU3+U2w=="}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_3.0.0_1567675864769_0.1192143829138892"},"_hasShrinkwrap":false},"3.0.1":{"name":"react-model","version":"3.0.1","description":"The State management library for React","main":"./dist/react-model.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^4.0.0"},"peerDependencies":{"react":"^16.3.0","react-dom":"^16.3.0"},"devDependencies":{"@commitlint/cli":"^8.1.0","@commitlint/config-conventional":"^8.1.0","@testing-library/react":"^9.1.3","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/jest":"^24.0.5","@types/node":"^12.7.4","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","husky":"^3.0.0","jest":"^24.1.0","prettier":"^1.16.4","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^7.0.0","remark-lint":"^6.0.4","remark-preset-lint-recommended":"^3.0.2","ts-jest":"^24.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"gitHead":"2755a0438bb1347deee454d52b3d84ee7388bf2a","_id":"react-model@3.0.1","_nodeVersion":"12.10.0","_npmVersion":"6.11.3","dist":{"integrity":"sha512-v3M49cJ//bicNqVkK/oTUFMTmorBZSNvvZ5YJiaaZRADgKi2rsPleOKthWBN+E7Ndyble9G9arjkc1aTbJLTzQ==","shasum":"471256f6a0bd62b58a94f4cee7239ba44062a7f3","tarball":"https://registry.npmjs.org/react-model/-/react-model-3.0.1.tgz","fileCount":24,"unpackedSize":76667,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJdiK/TCRA9TVsSAnZWagAA8FAP/RXuj0gFvet3JW+5EZQB\nNQVJAmTux/j1s9r7eioeFLh9k36mpG3owmEQ+K+6LJlg7yXF4B63sDTlen1/\nV/dWYimzhrnhxY668HzPiy0GQfGFVsC3avjoWhVdCqkte/vcrSzzffvnGnEt\nVH5IsMl9I01TS+wlFB7BZJ3M9pJadZR+BlMB+co0F/iIngncMh0mYPJWnJt6\ni6Xquo27EocspBhTJoKPcip0kjcDIjPhc8rhT+Km+PoytyWoe1QqtZuvxEtV\njSpOdXS2bCcrkSUTK7sT0WGOEnq287zw5UgwomOP3WNJ+gVoZefMm97xlM3v\n/xRbtRD7teuZ9IPlTFvvu7YOUF1WGBN0qV3AFTFxHmyFZdOmwfqs3oxfrrxE\ntMGXM7A/kECPQTPHdx/lJePK1hQcZE5cBnkJuyNNvTzeEO1yJxW4nGc07J7r\nVMiIFJ+1mWrawnrrfeZKzwXSHqW7ugrIJq+SAjsDDcnKom+5zgZEM+4v28pA\nlI8XKY1Z71WdRA+VG/BFzJDaH3ypf8shTlH9PIqMhNhcHdVKPMYp1AYswKnQ\ndGF1UNWFsWDB4PuLneu+zHU8LdVTTxcYRWjIV3ARU1Cr59trJ/MTBgeYMWc/\nr5LIhLgEFzQ0w52PLF9EvGD0CLy+ZTfnQoQXL71xt44J8lBPuVRB1aae8kp/\nBNkm\r\n=bN5v\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIG9DE6teVxo022wpcHbUWf3iny1lPCElK0XMz6vlQMrbAiBGL15Yv5A6WXl0o/1KN1mQlxX5HZHzDY5oDuzeMKlnxg=="}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_3.0.1_1569238995105_0.6838409551518174"},"_hasShrinkwrap":false},"3.0.2":{"name":"react-model","version":"3.0.2","description":"The State management library for React","main":"./dist/react-model.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^4.0.0"},"peerDependencies":{"react":"^16.3.0","react-dom":"^16.3.0"},"devDependencies":{"@commitlint/cli":"^8.1.0","@commitlint/config-conventional":"^8.1.0","@testing-library/react":"^9.1.3","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/jest":"^24.0.5","@types/node":"^12.7.4","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","husky":"^3.0.0","jest":"^24.1.0","prettier":"^1.16.4","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^7.0.0","remark-lint":"^6.0.4","remark-preset-lint-recommended":"^3.0.2","ts-jest":"^24.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"gitHead":"7a9ee87ad5437d089c1e805315af7d71729a0188","_id":"react-model@3.0.2","_nodeVersion":"12.10.0","_npmVersion":"6.11.3","dist":{"integrity":"sha512-FO4KqqrLp6PMSP+R3RPx0c6g4wzdr1eZeDW/6ojHD4suATS7OYAxHfQrtHP/HpDzf1TjHyg4xncMVRuf1Msh2w==","shasum":"a82ae75d60099db6a4e5bc9b8ea64ad8068e31ae","tarball":"https://registry.npmjs.org/react-model/-/react-model-3.0.2.tgz","fileCount":24,"unpackedSize":77121,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJdnDSCCRA9TVsSAnZWagAAH3EQAJ/FId/5RJ0QJIFNlAnp\nx0GQB2uSuE60J9xsfyG+UFZsGOV163Q2VGHbje0cWPyt+75TaGjdPN2Qb7as\nNnbsP7ssBe2qskjHLtWRazuWsosCNgCiUysGNXPyUPIpmMzIC93r02XT32kc\nTdtPXxEBDFlU1gsysBnOLt/Tf8MzWhZn5YLVwWoEuWbA4Vea4BnvraWm3auQ\ntAqQHTq9Hcp/ao1poEDEa0JC7Y7IL3vV+B5sI3MBqCUulHnnJH1WUMOkra+7\n/7YMhrwQnP2CJLMBcmtcovS8GCZY/MXtVijXxm/aW7Xso4LNN9xmmmi/ZRb3\n8jdAyxW2dTsHYO0GtvAhrdYo9BDmw8ue2nSt1vBbtHeT556fCZlJwblFWZMQ\nofuqmnb+gPW0kQOgEPmvBpCwlJV97RMhEq7HY5kdqtZB0ZP1XsQk+jtNzlzE\nhFYlfN/V/iBLWquNYmcthhJqdEpEMymsmh+KzqHONYWsTO1YHkV1vOPB05y3\no2UGcnyhSe6QImey6cd8St9p52LwXASIfPO/wyulLR4Mx05GEmZ2IIZYFDCj\ns94Y/Vsfdbc7J66vxwTN4h776ZZY+5T5UwmbSV8izVYhRuzsHoyZgvhlbH6C\nKLBliE6vnDHiLiexS5qqtmeYCyDRP/gjNB5Fo4+m62tlgWU+DmehK4EgkFQP\ntQ+4\r\n=4Kd2\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQCBiw311w/9eRvnv+qoI9qd3G67KPlkFx5X3HIqAUcuKAIhANrtOyxyyCM4OcWAYZBHT/iUVQ0nPgnWTmR8nBcsmPjD"}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_3.0.2_1570518145902_0.3012389516617313"},"_hasShrinkwrap":false},"3.0.3":{"name":"react-model","version":"3.0.3","description":"The State management library for React","main":"./dist/react-model.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^4.0.0"},"peerDependencies":{"react":"^16.3.0","react-dom":"^16.3.0"},"devDependencies":{"@commitlint/cli":"^8.1.0","@commitlint/config-conventional":"^8.1.0","@testing-library/react":"^9.1.3","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/jest":"^24.0.5","@types/node":"^12.7.4","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","husky":"^3.0.0","jest":"^24.1.0","prettier":"^1.16.4","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^7.0.0","remark-lint":"^6.0.4","remark-preset-lint-recommended":"^3.0.2","ts-jest":"^24.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"gitHead":"889701614bf0243e1c4e0c132cb552e6c1ce4a4e","_id":"react-model@3.0.3","_nodeVersion":"12.10.0","_npmVersion":"6.12.0","dist":{"integrity":"sha512-3r9M832QLAHhcMEDBfdZ3R9A0LmJ9QMC1m7mf5fQl+tcaCQvKs8zHg6XO8l3LdrVdO53ipgqwv8CAJdGaxbm3g==","shasum":"b2199aa935f7035508052e61f09ffa15da708800","tarball":"https://registry.npmjs.org/react-model/-/react-model-3.0.3.tgz","fileCount":24,"unpackedSize":77355,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJdsCPmCRA9TVsSAnZWagAAdUgP/1EQeb4bN0OcIYl7AXQk\nZc7UHovNB5mLg7Ju/IcDDwRrbkIuwrCPkF1zTGTttRgs7j69v+nrhwnBLPWx\nswuUyXkHsG0siG1Ku4JShkPdXGulXOi9MJa8vy+7cYYAkugcbgwlfdp0mK8t\nOXEyN/d4Vt2/GQCgR0BRiXtFldLV6n/1IcWDY5pWJe1T3+ylx2to8VjG/IEd\nfx9XSWgxf7bzr0/NQyxAeygLSnphAdd9LzT9HWphq34FVTZMyhKqJq3X1Lo+\nsdtfwu4l3m+/nmLPAyg8zFIVQU8mOImPKVaTQTNEe/G70dhIao9ENkIJ9mYa\nX0b8fCnXziqSmB1aH071Q1LCFJTFmxeapWvdtdg2U/Nnj9YOnz+HsK4fd5hq\nkNR1URx68bBjwTXZ77eBQE3kbt1wBAKOVGC09tN5iIIt3uy4QUdg4EC9ACqj\nmdxQHop9UjFVtVW10lROOXz7Hoj9CwQnTHsbI2hdImaauxMVbFCm8DsedI5A\n0Aa/f3647Cz43LLRxy7wle7t6s40rt6qrmG8Cg6T8KtX+iVU7FH99lSWaDN5\nVZWEub4ccmm6OBZ3ugYMeqhGx3h0JoESca9OeNixLpU7SoH59GrzGos8W1A6\nSlwckIynHgdx6O23uGdVOxJgaJKIl9ogRFZjv6MStlGrlnL5lUPFZF2+FLIT\ngmbX\r\n=tQvu\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQD0wZL9VaznnYeV9LHOY5BZ1fL00dg7Vw2jVN6lG5J3aQIhAIy+W0m7MjUsVhaucHLGXjgDzBmA5xFfvs7sMlQ5WsCt"}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_3.0.3_1571824613934_0.33194311444856095"},"_hasShrinkwrap":false},"3.0.4-rc.0":{"name":"react-model","version":"3.0.4-rc.0","description":"The State management library for React","main":"./dist/react-model.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^6.0.0"},"peerDependencies":{"react":"^16.3.0","react-dom":"^16.3.0"},"devDependencies":{"@commitlint/cli":"^8.1.0","@commitlint/config-conventional":"^8.1.0","@testing-library/react":"^9.1.3","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/jest":"^25.1.0","@types/node":"^13.1.0","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","husky":"^4.0.2","jest":"^24.1.0","prettier":"^1.16.4","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^7.0.0","remark-lint":"^6.0.4","remark-preset-lint-recommended":"^3.0.2","ts-jest":"^25.1.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"readme":"# react-model &middot; ![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) [![Build Status](https://travis-ci.org/byte-fe/react-model.svg?branch=master)](https://travis-ci.org/byte-fe/react-model) [![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⚔ Fully TypeScript Support\n\n📦 Built with microbundle\n\n⚙️ Middlewares Pipline ( redux-devtools support ... )\n\n☂️ 100% test coverage, safe on production\n\n🐛 Debug easily on test environment\n\n```tsx\nimport { Model } from 'react-model'\n\n// define model\nconst Todo = {\n  state: {\n    items: ['Install react-model', 'Read github docs', 'Build App']\n  },\n  actions: {\n    add: todo => {\n      // s is the readonly version of state\n      // you can also return partial state here but don't need to keep immutable manually\n      // state is the mutable state\n      return state => {\n        state.items.push(todo)\n      }\n    }\n  }\n}\n\n// Model Register\nconst { useStore } = Model(Todo)\n\nconst App = () => {\n  return <TodoList />\n}\n\nconst TodoList = () => {\n  const [state, actions] = useStore()\n  return <div>\n    <Addon handler={actions.add} />\n    {state.items.map((item, index) => (<Todo key={index} item={item} />))}\n  </div>\n}\n```\n\n---\n\n## Recently Updated\n\n* [Expand Context](#expand-context)\n* [ModelType Update: NextModelType => ModelType](#model)\n\n## Quick Start\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  - [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  - [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  - [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### Model\n\nEvery model have their 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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (payload, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    },\n    openLight: async (_, { state, actions }) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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(model)\n\n// You can use these types when use Class Components.\n// type ConsumerActionsType = getConsumerActionsType<typeof Model.actions>\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 keep the state and actions in the separate private store. 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 return from useStore can invoke the 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 () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button onClick={e => actions.increment(33)}>home increment</button>\n      <button onClick={e => sharedActions.increment(20)}>\n        shared increment\n      </button>\n      <button onClick={e => actions.get()}>fake request</button>\n      <button onClick={e => actions.openLight()}>fake nested call</button>\n    </div>\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 with depActions and React.memo to prevent child components rerender frequently.](https://github.com/ArrayZoneYour/react-model-todomvc/blob/master/src/components/TodoItem.tsx)\n\n[Demo Repo](https://github.com/ArrayZoneYour/react-model-todomvc)\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> Hint: The state returned should only be used as readonly\n\n```jsx\nimport { useStore, getState } from '../model/index'\n\nconst BasicHook = () => {\n  const [state, actions] = useStore('Counter')\n  useEffect(() => {\n    console.log('some mounted actions from BasicHooks')\n    return () =>\n      console.log(\n        `Basic Hooks unmounted, current Counter state: ${JSON.stringify(\n          getState('Counter')\n        )}`\n      )\n  }, [])\n  return (\n    <>\n      <div>state: {JSON.stringify(state)}</div>\n    </>\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: () => {\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 = () => {\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (params, { state: s }) => {\n      // issue: https://github.com/Microsoft/TypeScript/issues/29196\n      // async function return produce need define type manually.\n      return (state: typeof s) => {\n        state.counter += params || 1\n      }\n    },\n    decrease: params => s => {\n      s.counter += params || 1\n    }\n  }\n}\n\nexport default Model(model)\n```\n\nJavaScript Example\n\n```js\nconst Model = {\n  actions: {\n    increment: async (params) => {\n      return state => {\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<details>\n<summary>Store: shared.ts</summary>\n<p>\n\n```ts\nconst initialState = {\n  counter: 0\n}\n\nconst model: ModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: (params, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    }\n  },\n  // Provide for SSR\n  asyncState: async context => {\n    await waitFor(4000)\n    return { counter: 500 }\n  },\n  state: initialState\n}\n\nexport default Model(model)\n```\n\n</p>\n</details>\n\n<details>\n<summary>Global Config: _app.tsx</summary>\n<p>\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) => {\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    <Container>\n      <Component {...pageProps} />\n    </Container>\n  )\n}\n\nMyApp.getInitialProps = async (context: NextAppContext) => {\n  if (!(process as any).browser) {\n    const initialModels = context.Component.getInitialProps\n      ? await context.Component.getInitialProps(context.ctx)\n      await getInitialState() // get all model initialState\n      // : await getInitialState({ modelName: 'Home' }) // get Home initialState only\n      // : await getInitialState({ modelName: ['Home', 'Todo'] }) // get multi initialState\n      // : await getInitialState({ data }) // You can also pass some public data as asyncData params.\n    return { initialModels }\n  } else {\n    return { persistModel }\n  }\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Page: hooks/index.tsx</summary>\n<p>\n\n```tsx\nimport { useStore, getState } from '../index'\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button\n        onClick={e => {\n          actions.increment(33)\n        }}\n      >\n    </div>\n  )\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Single Page Config: benchmark.tsx</summary>\n<p>\n\n```tsx\n// ...\nBenchmark.getInitialProps = async () => {\n  return await getInitialState({ modelName: 'Todo' })\n}\n```\n</p>\n</details>\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<{}> = async (context, restMiddlewares) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: any) => 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) => async (params: any) => {\n  const context: Context = {\n    modelName,\n    setState,\n    actionName: action.name,\n    next: () => {},\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<\n  { name: string }, // State Type\n  { ext: undefined }, // ActionParamsType\n  { name: string } // ExtContextType\n> = {\n  actions: {\n    // { state, action } => { state, action, [name] }\n    ext: (_, { name }) => {\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 => '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      <Provider>\n        <Counter />\n      </Provider>\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<details>\n<summary>Javascript decorator version</summary>\n<p>\n\n```jsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\n\nconst mapProps = ({ light, counter }) => ({\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      <>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(5)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n      </>\n    )\n  }\n}\n```\n\n</p>\n</details>\n\n<details>\n<summary>TypeScript Version</summary>\n<p>\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) => ({\n  lightStatus: light ? 'open' : 'close',\n  counter,\n  response\n})\n\ntype RType = ReturnType<typeof mapProps>\n\nclass TSCounter extends PureComponent<\n  { state: RType } & { actions: ActionType }\n> {\n  render() {\n    const { state, actions } = this.props\n    return (\n      <>\n        <div>TS Counter</div>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(3)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n        <button onClick={e => actions.get()}>Get Response</button>\n        <div>message: {JSON.stringify(state.response)}</div>\n      </>\n    )\n  }\n}\n\nexport default connect(\n  'Home',\n  mapProps\n)(TSCounter)\n```\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\n\n## FAQ\n\n### How can I disable the console debugger\n\nJust remove consoleDebugger middleware.\n\n```typescript\nimport { actionMiddlewares } from 'react-model'\n// Find the index of middleware\nconst consoleDebuggerMiddlewareIndex = actionMiddlewares.indexOf(\n  middlewares.consoleDebugger\n)\n// Remove it\nactionMiddlewares.splice(consoleDebuggerMiddlewareIndex, 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) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: Error) => 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) => {\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      <div className=\"App\">\n        <Counter />\n        <Counter />\n        <Counter />\n      </div>\n    )\n  }\n}\n```\n\n<details>\n<summary>Counter model</summary>\n<p>\n\n```ts\ninterface State {\n  count: number\n}\n\ninterface ActionParams {\n  increment: number\n}\n\nconst model: ModelType<State, ActionParams> = {\n  state: {\n    count: 0\n  },\n  actions: {\n    increment: payload => {\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 => (state.count += payload)\n      return state => {\n        state.count += payload\n      }\n    }\n  }\n}\n\n```\n\n</p>\n</details>\n\n<details>\n<summary>Counter.tsx</summary>\n<p>\n\n```tsx\n\nconst Counter = () => {\n  const [{ useStore }] = useState(() => Model(model))\n  const [state, actions] = useStore()\n  return (\n    <div>\n      <div>{state.count}</div>\n      <button onClick={() => actions.increment(3)}>Increment</button>\n    </div>\n  )\n}\n\nexport default Counter\n```\n\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\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 => {\n    // Not allowed\n    // return state => (state.count += payload)\n    return state => {\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) => {\n  await timeout(1000, {})\n  context.next(restMiddlewares)\n}\n\nconst nextCounterModel: ModelType<CounterState, NextCounterActionParams> = {\n  actions: {\n    add: num => {\n      return state => {\n        state.count += num\n      }\n    },\n    increment: async (num, { actions }) => {\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","readmeFilename":"README.md","gitHead":"f347240cf8e53de42ff5c2762ad809d17010d516","_id":"react-model@3.0.4-rc.0","_nodeVersion":"12.10.0","_npmVersion":"6.13.7","dist":{"integrity":"sha512-BGdASTlO+TF7RtLMiRiMFeIxw2RWv23cjw4HpGX0VfM+EzfduzXEvj9zJZwUZW+WoneYOO147qvXzMq3mQh73Q==","shasum":"fafba7d4cf34c99697332f2907b737fe702c4152","tarball":"https://registry.npmjs.org/react-model/-/react-model-3.0.4-rc.0.tgz","fileCount":24,"unpackedSize":78825,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJeZ0XRCRA9TVsSAnZWagAAH6wP/AzKxL21eu5Qfkzc1pIw\n48Tz0LR0nOjDFYMOSF9lTw0rb2MeOf9XwvYz3nbm3GkhLndsef+WBJ+JPW33\nEwt1yn4pidf5JYAYZ/ttLe7Lygywp5vlf6E7pHTkcfUsNXsmZjujSg2Pzmal\nvqYUKSNygfIRWyI38aBDqtQV+Wl+2gZVlwo8tgUvD9hRGqXdczoBHyNnC5or\nshVd+o3KCoKiie8pAIUyWMeFGRQ1w4CT5r6dBAoSjwM9zqeHNb3G49EGXD4t\nj1Gx7SauGgg78VVqc/y4t8j2/YrTVgysjq2gGGmFrq49wCdC89vrBtoSsLH3\nLaheItQg7Va7GGOnQXiBYIfI8etpmD0wk5OCKpoAobxVNxInYtB+dCWXZwjR\nZTeGHg6Yt6iaLIkqAEodn3BAIto95X2x4Krh19gryqq4WmSnaWpUkZvzAs2c\nhUumLvH3kup0UeJ5+eR9y8hq9Fq9ExxtY++uTGWa4wJrAlp3zBA/Sd5WRnGk\nexj7qW9zsDuHx5HRAZQ8dvNjMElLCHrUbQfptBhy+XUQB+wIKdrlOtSE1bJE\nuzGd1x7CLPnILLtYRDjFcO4IqgTYlyfxh2cCNPQvffdRbhEU7AhCxkkcViiU\nQ3D2dCcGx9F9QcWwUDupZsV+KaioLrUQ1MFZfWJBjXBQzMdT8SH1Hj7A0ky7\n+vZ6\r\n=38pe\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCKRVSIREqKxNO13IyNaxjejSpfNrmOhDEX1RyhE0n2lwIgLnPG1oLlk40/0TncgE8equy5vU6I7754Jk+DY87BCV4="}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_3.0.4-rc.0_1583826384875_0.08913065785025331"},"_hasShrinkwrap":false},"3.0.4":{"name":"react-model","version":"3.0.4","description":"The State management library for React","main":"./dist/react-model.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^6.0.0"},"peerDependencies":{"react":"^16.3.0","react-dom":"^16.3.0"},"devDependencies":{"@commitlint/cli":"^8.1.0","@commitlint/config-conventional":"^8.1.0","@testing-library/react":"^9.1.3","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/jest":"^25.1.0","@types/node":"^13.1.0","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","husky":"^4.0.2","jest":"^24.1.0","prettier":"^1.16.4","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^7.0.0","remark-lint":"^6.0.4","remark-preset-lint-recommended":"^3.0.2","ts-jest":"^25.1.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"gitHead":"f917376692bfd80d3a510e87fbb71ae9f0ce4584","_id":"react-model@3.0.4","_nodeVersion":"12.10.0","_npmVersion":"6.13.7","dist":{"integrity":"sha512-g1R5Ocj9ZgWX6M2VMWtEsaU/LoWXoIq9L7i7eJi2tF8uu8Tmmb5K+YdOina7s2o8DbyFVyt4StAgdnykWKScYQ==","shasum":"89cd1a22485f243c0e832652291aef7a62366f3c","tarball":"https://registry.npmjs.org/react-model/-/react-model-3.0.4.tgz","fileCount":24,"unpackedSize":79482,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJeZ2ayCRA9TVsSAnZWagAAtW8P/3UVcK/5wuLu3hr3VdqW\nFZyrjKSIV3w96P2BbYRu5l4HCyixxAo4q8NRy62DgeMbm6SP50c/YcXS8Z0X\nxeqF2kj8WjioFyK6aQ0M+eDW3gtiisC2BaNhbj0jGdyk3ah8rjlwzhVZsWT7\n9/Amw6rGldqAtJakRyygIB/Ss82nnGgxeZ66B9huQGKzaQdlewC4yRQC6toV\nAPeRbgYNnLp9YU47YvwyQMPhR9u+t6BolxzNq3AWPzpzOcTyQDvZGe9tl4cX\nBC1dFFs53KMd/97sMYIlsj9aNU+XcozSVjFDcAUsWC3JwU8fi+BlXhd0/e0K\nfPWchvP8rWk38wwcggK6OJzf30qm1tsDnyus+eS9yzeYmEP54c1hEemypk3f\ng8Til91YiH7gHIbay4KWqiMQup3ZmPuBGJ1FQTxVQxSDrKkNRNDzzHowF/FA\n+eSDk8aUfR2N/aGpGnj4mhEco35ATic1qTLLQj30WtphBGeJAGaCYgUGqE+W\nDKSKbF5q7xzVGrXN8tkNR4mWzWUs48paUVhEDAXgcKZUqXQKVPIs+96YWT4x\nc3Rq94wbNfISw/SgA0DTxXIBRt2GXnND3z6GYW9JxT68XOTKebf2adHwhb5G\nEnyXwRfVRyhAong0tQyeoZ4i/X0auMJxkD4RYDkySGhNeCf6H+sji2iwgO12\naPVK\r\n=9wav\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIGF/zrMhaZaxaDy2J9aoE3knx/TJFYNkFyGLrcUerdOeAiBEFpwjAEK4Kw5QkQm25ylhV09/wuZcS4YksXvVCceGjg=="}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_3.0.4_1583834802030_0.8706602699107717"},"_hasShrinkwrap":false},"3.1.0-dev.20200313.0":{"name":"react-model","version":"3.1.0-dev.20200313.0","description":"The State management library for React","main":"./dist/react-model.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^6.0.0"},"peerDependencies":{"react":"^16.3.0","react-dom":"^16.3.0"},"devDependencies":{"@commitlint/cli":"^8.1.0","@commitlint/config-conventional":"^8.1.0","@testing-library/react":"^9.1.3","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/jest":"^25.1.0","@types/node":"^13.1.0","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","husky":"^4.0.2","jest":"^24.1.0","prettier":"^1.16.4","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^7.0.0","remark-lint":"^6.0.4","remark-preset-lint-recommended":"^3.0.2","ts-jest":"^25.1.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"readme":"# react-model &middot; ![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) [![Build Status](https://travis-ci.org/byte-fe/react-model.svg?branch=master)](https://travis-ci.org/byte-fe/react-model) [![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⚔ Fully TypeScript Support\n\n📦 Built with microbundle\n\n⚙️ Middlewares Pipline ( redux-devtools support ... )\n\n☂️ 100% test coverage, safe on production\n\n🐛 Debug easily on test environment\n\n```tsx\nimport { Model } from 'react-model'\n\n// define model\nconst Todo = {\n  state: {\n    items: ['Install react-model', 'Read github docs', 'Build App']\n  },\n  actions: {\n    add: todo => {\n      // s is the readonly version of state\n      // you can also return partial state here but don't need to keep immutable manually\n      // state is the mutable state\n      return state => {\n        state.items.push(todo)\n      }\n    }\n  }\n}\n\n// Model Register\nconst { useStore } = Model(Todo)\n\nconst App = () => {\n  return <TodoList />\n}\n\nconst TodoList = () => {\n  const [state, actions] = useStore()\n  return <div>\n    <Addon handler={actions.add} />\n    {state.items.map((item, index) => (<Todo key={index} item={item} />))}\n  </div>\n}\n```\n\n---\n\n## Recently Updated\n\n* [Expand Context](#expand-context)\n* [ModelType Update: NextModelType => ModelType](#model)\n\n## Quick Start\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  - [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  - [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  - [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### Model\n\nEvery model have their 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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (payload, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    },\n    openLight: async (_, { state, actions }) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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(model)\n\n// You can use these types when use Class Components.\n// type ConsumerActionsType = getConsumerActionsType<typeof Model.actions>\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 keep the state and actions in the separate private store. 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 return from useStore can invoke the 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 () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button onClick={e => actions.increment(33)}>home increment</button>\n      <button onClick={e => sharedActions.increment(20)}>\n        shared increment\n      </button>\n      <button onClick={e => actions.get()}>fake request</button>\n      <button onClick={e => actions.openLight()}>fake nested call</button>\n    </div>\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 with depActions and React.memo to prevent child components rerender frequently.](https://github.com/ArrayZoneYour/react-model-todomvc/blob/master/src/components/TodoItem.tsx)\n\n[Demo Repo](https://github.com/ArrayZoneYour/react-model-todomvc)\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> Hint: The state returned should only be used as readonly\n\n```jsx\nimport { useStore, getState } from '../model/index'\n\nconst BasicHook = () => {\n  const [state, actions] = useStore('Counter')\n  useEffect(() => {\n    console.log('some mounted actions from BasicHooks')\n    return () =>\n      console.log(\n        `Basic Hooks unmounted, current Counter state: ${JSON.stringify(\n          getState('Counter')\n        )}`\n      )\n  }, [])\n  return (\n    <>\n      <div>state: {JSON.stringify(state)}</div>\n    </>\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: () => {\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 = () => {\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (params, { state: s }) => {\n      // issue: https://github.com/Microsoft/TypeScript/issues/29196\n      // async function return produce need define type manually.\n      return (state: typeof s) => {\n        state.counter += params || 1\n      }\n    },\n    decrease: params => s => {\n      s.counter += params || 1\n    }\n  }\n}\n\nexport default Model(model)\n```\n\nJavaScript Example\n\n```js\nconst Model = {\n  actions: {\n    increment: async (params) => {\n      return state => {\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<details>\n<summary>Store: shared.ts</summary>\n<p>\n\n```ts\nconst initialState = {\n  counter: 0\n}\n\nconst model: ModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: (params, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    }\n  },\n  // Provide for SSR\n  asyncState: async context => {\n    await waitFor(4000)\n    return { counter: 500 }\n  },\n  state: initialState\n}\n\nexport default Model(model)\n```\n\n</p>\n</details>\n\n<details>\n<summary>Global Config: _app.tsx</summary>\n<p>\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) => {\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    <Container>\n      <Component {...pageProps} />\n    </Container>\n  )\n}\n\nMyApp.getInitialProps = async (context: NextAppContext) => {\n  if (!(process as any).browser) {\n    const initialModels = context.Component.getInitialProps\n      ? await context.Component.getInitialProps(context.ctx)\n      await getInitialState() // get all model initialState\n      // : await getInitialState({ modelName: 'Home' }) // get Home initialState only\n      // : await getInitialState({ modelName: ['Home', 'Todo'] }) // get multi initialState\n      // : await getInitialState({ data }) // You can also pass some public data as asyncData params.\n    return { initialModels }\n  } else {\n    return { persistModel }\n  }\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Page: hooks/index.tsx</summary>\n<p>\n\n```tsx\nimport { useStore, getState } from '../index'\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button\n        onClick={e => {\n          actions.increment(33)\n        }}\n      >\n    </div>\n  )\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Single Page Config: benchmark.tsx</summary>\n<p>\n\n```tsx\n// ...\nBenchmark.getInitialProps = async () => {\n  return await getInitialState({ modelName: 'Todo' })\n}\n```\n</p>\n</details>\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<{}> = async (context, restMiddlewares) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: any) => 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) => async (params: any) => {\n  const context: Context = {\n    modelName,\n    setState,\n    actionName: action.name,\n    next: () => {},\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<\n  { name: string }, // State Type\n  { ext: undefined }, // ActionParamsType\n  { name: string } // ExtContextType\n> = {\n  actions: {\n    // { state, action } => { state, action, [name] }\n    ext: (_, { name }) => {\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 => '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      <Provider>\n        <Counter />\n      </Provider>\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<details>\n<summary>Javascript decorator version</summary>\n<p>\n\n```jsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\n\nconst mapProps = ({ light, counter }) => ({\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      <>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(5)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n      </>\n    )\n  }\n}\n```\n\n</p>\n</details>\n\n<details>\n<summary>TypeScript Version</summary>\n<p>\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) => ({\n  lightStatus: light ? 'open' : 'close',\n  counter,\n  response\n})\n\ntype RType = ReturnType<typeof mapProps>\n\nclass TSCounter extends PureComponent<\n  { state: RType } & { actions: ActionType }\n> {\n  render() {\n    const { state, actions } = this.props\n    return (\n      <>\n        <div>TS Counter</div>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(3)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n        <button onClick={e => actions.get()}>Get Response</button>\n        <div>message: {JSON.stringify(state.response)}</div>\n      </>\n    )\n  }\n}\n\nexport default connect(\n  'Home',\n  mapProps\n)(TSCounter)\n```\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\n\n## FAQ\n\n### How can I disable the console debugger\n\nJust remove consoleDebugger middleware.\n\n```typescript\nimport { actionMiddlewares } from 'react-model'\n// Find the index of middleware\nconst consoleDebuggerMiddlewareIndex = actionMiddlewares.indexOf(\n  middlewares.consoleDebugger\n)\n// Remove it\nactionMiddlewares.splice(consoleDebuggerMiddlewareIndex, 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) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: Error) => 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) => {\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      <div className=\"App\">\n        <Counter />\n        <Counter />\n        <Counter />\n      </div>\n    )\n  }\n}\n```\n\n<details>\n<summary>Counter model</summary>\n<p>\n\n```ts\ninterface State {\n  count: number\n}\n\ninterface ActionParams {\n  increment: number\n}\n\nconst model: ModelType<State, ActionParams> = {\n  state: {\n    count: 0\n  },\n  actions: {\n    increment: payload => {\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 => (state.count += payload)\n      return state => {\n        state.count += payload\n      }\n    }\n  }\n}\n\n```\n\n</p>\n</details>\n\n<details>\n<summary>Counter.tsx</summary>\n<p>\n\n```tsx\n\nconst Counter = () => {\n  const [{ useStore }] = useState(() => Model(model))\n  const [state, actions] = useStore()\n  return (\n    <div>\n      <div>{state.count}</div>\n      <button onClick={() => actions.increment(3)}>Increment</button>\n    </div>\n  )\n}\n\nexport default Counter\n```\n\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\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 => {\n    // Not allowed\n    // return state => (state.count += payload)\n    return state => {\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) => {\n  await timeout(1000, {})\n  context.next(restMiddlewares)\n}\n\nconst nextCounterModel: ModelType<CounterState, NextCounterActionParams> = {\n  actions: {\n    add: num => {\n      return state => {\n        state.count += num\n      }\n    },\n    increment: async (num, { actions }) => {\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","readmeFilename":"README.md","gitHead":"a217007d8d61294201d78af0d0fa15652115a748","_id":"react-model@3.1.0-dev.20200313.0","_nodeVersion":"12.10.0","_npmVersion":"6.13.7","dist":{"integrity":"sha512-cYGg6IdoDgSj/A4w1jvk8ChBV+AMAfKwO2JT6BsDqR8bJDlzeSmcwSATJGMG9XjDTdLZsvhc8oFNQS04Tthnag==","shasum":"e4aed95f82ab3793d8b425044b1efd6d02ca4153","tarball":"https://registry.npmjs.org/react-model/-/react-model-3.1.0-dev.20200313.0.tgz","fileCount":24,"unpackedSize":79961,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJea42WCRA9TVsSAnZWagAAUc0P/0mCVI+nU/Z3WFBWseGb\n99JmXKjydz7F7DK0qzGog+unYKakOyJF0LvFlPcVmaFT/rBpbBb2khGWUSKR\nTVOsypCEpdMs05U3XYw2PKvI+KE+Eh9Je5zpS6IC+d+WiQ4lkxSeKT5WKvZ5\nT1RSLralWEhxmtgQv0/D1to6q+EEG3mjJKZbYbE9tStK8mtsvCTYjmoShjqt\nZpPfAhTdf9CM6z++JA3+4BWRoYWT/laq/MTdpOvVPmgnf4LYXnIEtEj8pJAq\n7+d55TyFqAQH1TDdjllyqDjHI93obrjaWU6XyJIPIJZFoufI7IlV7588dELF\nL5ktf/d5wfS1hfmwHUXJgOuA3+GO/A6evvPCHnGrkjYYEsq4hLGDeQehD20R\nLxN9CBDYsP0PUpkIKxFv/KTX+9jm+65zmwROYWA2sM6FYMazhti8P80D7Qan\nRGnbN/2W+PMjOnTh9WWA38hxzxHLq+POvg9xucaUHLQhvYUnnuGQdJhmMRBp\nPN4TxPJ10Z1ABraCwxdyzFaniFUL8g6da3qLSR1kJchwLQlvUI7kSLXkUAgv\ntaL/dVasq4GnVYm8zNIFUG/m4maOm4hJB6Wc1f+vrU5G7gjIJZUGPoeuReeY\nb04KIzh+gJxoNb9fLl2BJy7KFYc+V+bL+SRbLvIx6VjId5b5RRk4YGuHvc8a\nh4w1\r\n=SdFr\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIDkpDJMXmVFknsTHWrrI2Wd0YeJluhOQFN6mMbbKh92iAiBesBPZCFsPzag658TmIE5vN7/YCN+RhLV/hOc4PejT1g=="}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_3.1.0-dev.20200313.0_1584106902374_0.7495266137242891"},"_hasShrinkwrap":false},"3.1.0-dev.20200313.1":{"name":"react-model","version":"3.1.0-dev.20200313.1","description":"The State management library for React","main":"./dist/react-model.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^6.0.0"},"peerDependencies":{"react":"^16.3.0","react-dom":"^16.3.0"},"devDependencies":{"@commitlint/cli":"^8.1.0","@commitlint/config-conventional":"^8.1.0","@testing-library/react":"^9.1.3","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/jest":"^25.1.0","@types/node":"^13.1.0","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","husky":"^4.0.2","jest":"^24.1.0","prettier":"^1.16.4","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^7.0.0","remark-lint":"^6.0.4","remark-preset-lint-recommended":"^3.0.2","ts-jest":"^25.1.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"readme":"# react-model &middot; ![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) [![Build Status](https://travis-ci.org/byte-fe/react-model.svg?branch=master)](https://travis-ci.org/byte-fe/react-model) [![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⚔ Fully TypeScript Support\n\n📦 Built with microbundle\n\n⚙️ Middlewares Pipline ( redux-devtools support ... )\n\n☂️ 100% test coverage, safe on production\n\n🐛 Debug easily on test environment\n\n```tsx\nimport { Model } from 'react-model'\n\n// define model\nconst Todo = {\n  state: {\n    items: ['Install react-model', 'Read github docs', 'Build App']\n  },\n  actions: {\n    add: todo => {\n      // s is the readonly version of state\n      // you can also return partial state here but don't need to keep immutable manually\n      // state is the mutable state\n      return state => {\n        state.items.push(todo)\n      }\n    }\n  }\n}\n\n// Model Register\nconst { useStore } = Model(Todo)\n\nconst App = () => {\n  return <TodoList />\n}\n\nconst TodoList = () => {\n  const [state, actions] = useStore()\n  return <div>\n    <Addon handler={actions.add} />\n    {state.items.map((item, index) => (<Todo key={index} item={item} />))}\n  </div>\n}\n```\n\n---\n\n## Recently Updated\n\n* [Expand Context](#expand-context)\n* [ModelType Update: NextModelType => ModelType](#model)\n\n## Quick Start\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  - [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  - [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  - [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### Model\n\nEvery model have their 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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (payload, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    },\n    openLight: async (_, { state, actions }) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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(model)\n\n// You can use these types when use Class Components.\n// type ConsumerActionsType = getConsumerActionsType<typeof Model.actions>\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 keep the state and actions in the separate private store. 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 return from useStore can invoke the 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 () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button onClick={e => actions.increment(33)}>home increment</button>\n      <button onClick={e => sharedActions.increment(20)}>\n        shared increment\n      </button>\n      <button onClick={e => actions.get()}>fake request</button>\n      <button onClick={e => actions.openLight()}>fake nested call</button>\n    </div>\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 with depActions and React.memo to prevent child components rerender frequently.](https://github.com/ArrayZoneYour/react-model-todomvc/blob/master/src/components/TodoItem.tsx)\n\n[Demo Repo](https://github.com/ArrayZoneYour/react-model-todomvc)\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> Hint: The state returned should only be used as readonly\n\n```jsx\nimport { useStore, getState } from '../model/index'\n\nconst BasicHook = () => {\n  const [state, actions] = useStore('Counter')\n  useEffect(() => {\n    console.log('some mounted actions from BasicHooks')\n    return () =>\n      console.log(\n        `Basic Hooks unmounted, current Counter state: ${JSON.stringify(\n          getState('Counter')\n        )}`\n      )\n  }, [])\n  return (\n    <>\n      <div>state: {JSON.stringify(state)}</div>\n    </>\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: () => {\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 = () => {\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (params, { state: s }) => {\n      // issue: https://github.com/Microsoft/TypeScript/issues/29196\n      // async function return produce need define type manually.\n      return (state: typeof s) => {\n        state.counter += params || 1\n      }\n    },\n    decrease: params => s => {\n      s.counter += params || 1\n    }\n  }\n}\n\nexport default Model(model)\n```\n\nJavaScript Example\n\n```js\nconst Model = {\n  actions: {\n    increment: async (params) => {\n      return state => {\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<details>\n<summary>Store: shared.ts</summary>\n<p>\n\n```ts\nconst initialState = {\n  counter: 0\n}\n\nconst model: ModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: (params, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    }\n  },\n  // Provide for SSR\n  asyncState: async context => {\n    await waitFor(4000)\n    return { counter: 500 }\n  },\n  state: initialState\n}\n\nexport default Model(model)\n```\n\n</p>\n</details>\n\n<details>\n<summary>Global Config: _app.tsx</summary>\n<p>\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) => {\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    <Container>\n      <Component {...pageProps} />\n    </Container>\n  )\n}\n\nMyApp.getInitialProps = async (context: NextAppContext) => {\n  if (!(process as any).browser) {\n    const initialModels = context.Component.getInitialProps\n      ? await context.Component.getInitialProps(context.ctx)\n      await getInitialState() // get all model initialState\n      // : await getInitialState({ modelName: 'Home' }) // get Home initialState only\n      // : await getInitialState({ modelName: ['Home', 'Todo'] }) // get multi initialState\n      // : await getInitialState({ data }) // You can also pass some public data as asyncData params.\n    return { initialModels }\n  } else {\n    return { persistModel }\n  }\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Page: hooks/index.tsx</summary>\n<p>\n\n```tsx\nimport { useStore, getState } from '../index'\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button\n        onClick={e => {\n          actions.increment(33)\n        }}\n      >\n    </div>\n  )\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Single Page Config: benchmark.tsx</summary>\n<p>\n\n```tsx\n// ...\nBenchmark.getInitialProps = async () => {\n  return await getInitialState({ modelName: 'Todo' })\n}\n```\n</p>\n</details>\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<{}> = async (context, restMiddlewares) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: any) => 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) => async (params: any) => {\n  const context: Context = {\n    modelName,\n    setState,\n    actionName: action.name,\n    next: () => {},\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<\n  { name: string }, // State Type\n  { ext: undefined }, // ActionParamsType\n  { name: string } // ExtContextType\n> = {\n  actions: {\n    // { state, action } => { state, action, [name] }\n    ext: (_, { name }) => {\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 => '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      <Provider>\n        <Counter />\n      </Provider>\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<details>\n<summary>Javascript decorator version</summary>\n<p>\n\n```jsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\n\nconst mapProps = ({ light, counter }) => ({\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      <>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(5)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n      </>\n    )\n  }\n}\n```\n\n</p>\n</details>\n\n<details>\n<summary>TypeScript Version</summary>\n<p>\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) => ({\n  lightStatus: light ? 'open' : 'close',\n  counter,\n  response\n})\n\ntype RType = ReturnType<typeof mapProps>\n\nclass TSCounter extends PureComponent<\n  { state: RType } & { actions: ActionType }\n> {\n  render() {\n    const { state, actions } = this.props\n    return (\n      <>\n        <div>TS Counter</div>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(3)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n        <button onClick={e => actions.get()}>Get Response</button>\n        <div>message: {JSON.stringify(state.response)}</div>\n      </>\n    )\n  }\n}\n\nexport default connect(\n  'Home',\n  mapProps\n)(TSCounter)\n```\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\n\n## FAQ\n\n### How can I disable the console debugger\n\nJust remove consoleDebugger middleware.\n\n```typescript\nimport { actionMiddlewares } from 'react-model'\n// Find the index of middleware\nconst consoleDebuggerMiddlewareIndex = actionMiddlewares.indexOf(\n  middlewares.consoleDebugger\n)\n// Remove it\nactionMiddlewares.splice(consoleDebuggerMiddlewareIndex, 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) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: Error) => 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) => {\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      <div className=\"App\">\n        <Counter />\n        <Counter />\n        <Counter />\n      </div>\n    )\n  }\n}\n```\n\n<details>\n<summary>Counter model</summary>\n<p>\n\n```ts\ninterface State {\n  count: number\n}\n\ninterface ActionParams {\n  increment: number\n}\n\nconst model: ModelType<State, ActionParams> = {\n  state: {\n    count: 0\n  },\n  actions: {\n    increment: payload => {\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 => (state.count += payload)\n      return state => {\n        state.count += payload\n      }\n    }\n  }\n}\n\n```\n\n</p>\n</details>\n\n<details>\n<summary>Counter.tsx</summary>\n<p>\n\n```tsx\n\nconst Counter = () => {\n  const [{ useStore }] = useState(() => Model(model))\n  const [state, actions] = useStore()\n  return (\n    <div>\n      <div>{state.count}</div>\n      <button onClick={() => actions.increment(3)}>Increment</button>\n    </div>\n  )\n}\n\nexport default Counter\n```\n\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\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 => {\n    // Not allowed\n    // return state => (state.count += payload)\n    return state => {\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) => {\n  await timeout(1000, {})\n  context.next(restMiddlewares)\n}\n\nconst nextCounterModel: ModelType<CounterState, NextCounterActionParams> = {\n  actions: {\n    add: num => {\n      return state => {\n        state.count += num\n      }\n    },\n    increment: async (num, { actions }) => {\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","readmeFilename":"README.md","gitHead":"a217007d8d61294201d78af0d0fa15652115a748","_id":"react-model@3.1.0-dev.20200313.1","_nodeVersion":"12.10.0","_npmVersion":"6.13.7","dist":{"integrity":"sha512-dVwXwE5eilWbmjaL/h4jWM5iQiUw5yL3uVJr+omkQyjfCqNHTMxi9aBeBvl6y+AsQtYQKl/e6zByvzyaKMVAvQ==","shasum":"d73cdc58ab2d828f8079650c8dc8479e5d1670af","tarball":"https://registry.npmjs.org/react-model/-/react-model-3.1.0-dev.20200313.1.tgz","fileCount":24,"unpackedSize":80869,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJea6UZCRA9TVsSAnZWagAADBsP/0JKfhdcyzHx+JdVHvBN\nTdmtAdr8SzuAgb2uJ0Hs9BTSauK6wLnkM6XqFh+CbpYZSsFsqjWWEzlbCfwl\n7ETJOAZqTbYvNPjvRNMl0F9oq+kE6gT/Z044Flqh4fURjME5Blbot6/0NAgA\nb0qRyC0cDqXCDisQ06bpDreKxUk4jXf/JvbW20x9uCD47tauOBpAwYmw/hZG\nhUvTKnCg9N6q6mdEs2IcWfUDjpNa0NK7XsRTSlQAjcv0CeinSwYRslYowF6O\nGnmIUJIT6olT+NXARYZMbq5fS2lRRLMyejaRk9fbh7nm/+cdvZXrgxVmzUXy\no3TnnNTqeloQkvaiwbumbWzWnqWA8cjKiWPLyfJQnasVTljDj8inp8QbQCTT\n3/sFQ5gbTI8+Yp5e64EZkmeRjXn5yeL/I0QVYDehyRFKTRGUX2rGnze7nh3E\nKGG9UrfS5tB454MnStl/TJtr4zw3r0IPbmGupRYgNtLpsCe0fhelu47msYs/\nrrqR53WGSE8atA/8XOAv1KA4kvVJyqzJiGtp/gEOuy4EgirHbrQz5yQwUzCw\npLN5QuRPcDOz2RtQn+2kbFlG3p+21i1YQRCj6XOOhRqnPFZ2ftYT7yx6Dmmq\njN5fsxfjECaJ6lcxScudiDll7lQeAS3kw/tWcekJdUs3vxzeNHKPNY5kTM9C\nQSkK\r\n=I+lI\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCHNCsowHMhmCXwmPVjD9K02Y3ij9rQ04lpEelpHwAa9QIgPeMY5DsSY1TcQp4QAXAMTM8L5yjghMLdEZGrsEhEiE4="}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_3.1.0-dev.20200313.1_1584112920687_0.2198665700843776"},"_hasShrinkwrap":false},"3.1.0":{"name":"react-model","version":"3.1.0","description":"The State management library for React","main":"./dist/react-model.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^6.0.0"},"peerDependencies":{"react":"^16.3.0","react-dom":"^16.3.0"},"devDependencies":{"@commitlint/cli":"^8.1.0","@commitlint/config-conventional":"^8.1.0","@testing-library/react":"^9.1.3","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/jest":"^25.1.0","@types/node":"^13.1.0","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","husky":"^4.0.2","jest":"^24.1.0","prettier":"^1.16.4","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^7.0.0","remark-lint":"^6.0.4","remark-preset-lint-recommended":"^3.0.2","ts-jest":"^25.1.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"gitHead":"537ee6881c5ee3948e473c6da269dc8981fc614a","_id":"react-model@3.1.0","_nodeVersion":"12.10.0","_npmVersion":"6.13.7","dist":{"integrity":"sha512-qRG2TMM8quE59TyjzftiF8bamf6X3xHHy1WaCV7CdcDXIkyVv5UfXFgkX7xjxQ548ClqgXQy0IEpQfYDFoBttw==","shasum":"0bb86514557187647e88b01d2773458a8b6711bc","tarball":"https://registry.npmjs.org/react-model/-/react-model-3.1.0.tgz","fileCount":24,"unpackedSize":80890,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJeeCR3CRA9TVsSAnZWagAA9k0P/A/WXDOHxVileJQl00DS\naK1udGiu2P/BseRKcmQzG4mGF89joeVSlim8uJPPPqxzDEnhuSeZLso/P2sw\nGsldR+bAIPc8lk8EVpYMF0YF0ZoCapE+BhSS3uTu5WQNAyw1O40flJqDa2X7\n8tTFYbnnS61rq86F6g6UApkXJpoub1o8z/c/5v46yL/BKFKuP7oUEAvSiZUk\ntI/dvPX3M9g6Fz6gijZg8YlnkwlKJY5ILsgny33l5VAW89zo+PQjuCI59Byf\nUW1I/6UmUosZl/aibn5VWCNi4WEd2QRWkDCnk79D52RjLvk44KqXNPozWlxt\n3erVkjRvliqfSSZD+UMhZobhTW4yLEKM1s4PhshTGxuoWq1+sRVawP2J5qFR\n67INHp8SCezENlmAIXFfm18BZL4TnOhj1c/no8XtCRViTIjYW+PvRqA4RMdE\nUnE6SGfvzxgbnZmqStWeunzYIQJ5kFgR/U103FEFN/Te2bG0e1uY3m5mu6uv\nOooXPCpW9RH2OfR7KQj8qdDxIYJDs9cq0O4WmwlUrcgdGOPEgZ/zVoA+nXbR\n/MRkVauv6kfgbS8fduRLbdl2Tgf26d6EFHuccKojJ1LGFIWlx6uTOgCr0cw5\ntL3u8t3nKlpukif0TKvugdhCSHVRHxCC5BjU93uxd800UE2DdEmECiJFL7Qo\n+XlS\r\n=UBI2\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDq3rFaZ8BqGhZNNUpbHFudMqOUdEtDkEKDY0gfFNudjQIgCwj5MKeWw6aUijcJWSwElvxD+vK7LdTXOdQnYl8VzJs="}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_3.1.0_1584931959321_0.4051230574660707"},"_hasShrinkwrap":false},"3.1.1-rc.0":{"name":"react-model","version":"3.1.1-rc.0","description":"The State management library for React","main":"./dist/react-model.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^6.0.0"},"peerDependencies":{"react":"^16.3.0","react-dom":"^16.3.0"},"devDependencies":{"@commitlint/cli":"^8.1.0","@commitlint/config-conventional":"^8.1.0","@testing-library/react":"^10.0.1","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/jest":"^25.1.0","@types/node":"^13.1.0","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","husky":"^4.0.2","jest":"^24.1.0","microbundle":"^0.11.0","prettier":"^2.0.0","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^7.0.0","remark-lint":"^6.0.4","remark-preset-lint-recommended":"^3.0.2","ts-jest":"^25.1.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"readme":"# react-model &middot; ![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) [![Build Status](https://travis-ci.org/byte-fe/react-model.svg?branch=master)](https://travis-ci.org/byte-fe/react-model) [![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⚔ Fully TypeScript Support\n\n📦 Built with microbundle\n\n⚙️ Middlewares Pipline ( redux-devtools support ... )\n\n☂️ 100% test coverage, safe on production\n\n🐛 Debug easily on test environment\n\n```tsx\nimport { Model } from 'react-model'\n\n// define model\nconst Todo = {\n  state: {\n    items: ['Install react-model', 'Read github docs', 'Build App']\n  },\n  actions: {\n    add: todo => {\n      // s is the readonly version of state\n      // you can also return partial state here but don't need to keep immutable manually\n      // state is the mutable state\n      return state => {\n        state.items.push(todo)\n      }\n    }\n  }\n}\n\n// Model Register\nconst { useStore } = Model(Todo)\n\nconst App = () => {\n  return <TodoList />\n}\n\nconst TodoList = () => {\n  const [state, actions] = useStore()\n  return <div>\n    <Addon handler={actions.add} />\n    {state.items.map((item, index) => (<Todo key={index} item={item} />))}\n  </div>\n}\n```\n\n---\n\n## Recently Updated\n\n* [SSR Support: only return asyncState from server side](#ssr-with-nextjs)\n* [Expand Context](#expand-context)\n\n## Quick Start\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  - [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  - [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  - [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### Model\n\nEvery model have their 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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (payload, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    },\n    openLight: async (_, { state, actions }) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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(model)\n\n// You can use these types when use Class Components.\n// type ConsumerActionsType = getConsumerActionsType<typeof Model.actions>\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 keep the state and actions in the separate private store. 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 return from useStore can invoke the 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 () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button onClick={e => actions.increment(33)}>home increment</button>\n      <button onClick={e => sharedActions.increment(20)}>\n        shared increment\n      </button>\n      <button onClick={e => actions.get()}>fake request</button>\n      <button onClick={e => actions.openLight()}>fake nested call</button>\n    </div>\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 with depActions and React.memo to prevent child components rerender frequently.](https://github.com/ArrayZoneYour/react-model-todomvc/blob/master/src/components/TodoItem.tsx)\n\n[Demo Repo](https://github.com/ArrayZoneYour/react-model-todomvc)\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> Hint: The state returned should only be used as readonly\n\n```jsx\nimport { useStore, getState } from '../model/index'\n\nconst BasicHook = () => {\n  const [state, actions] = useStore('Counter')\n  useEffect(() => {\n    console.log('some mounted actions from BasicHooks')\n    return () =>\n      console.log(\n        `Basic Hooks unmounted, current Counter state: ${JSON.stringify(\n          getState('Counter')\n        )}`\n      )\n  }, [])\n  return (\n    <>\n      <div>state: {JSON.stringify(state)}</div>\n    </>\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: () => {\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 = () => {\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (params, { state: s }) => {\n      // issue: https://github.com/Microsoft/TypeScript/issues/29196\n      // async function return produce need define type manually.\n      return (state: typeof s) => {\n        state.counter += params || 1\n      }\n    },\n    decrease: params => s => {\n      s.counter += params || 1\n    }\n  }\n}\n\nexport default Model(model)\n```\n\nJavaScript Example\n\n```js\nconst Model = {\n  actions: {\n    increment: async (params) => {\n      return state => {\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<details>\n<summary>Store: shared.ts</summary>\n<p>\n\n```ts\nconst initialState = {\n  counter: 0\n}\n\nconst model: ModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: (params, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    }\n  },\n  // Provide for SSR\n  asyncState: async context => {\n    await waitFor(4000)\n    return { counter: 500 }\n  },\n  state: initialState\n}\n\nexport default Model(model)\n```\n\n</p>\n</details>\n\n<details>\n<summary>Global Config: _app.tsx</summary>\n<p>\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) => {\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    <Container>\n      <Component {...pageProps} />\n    </Container>\n  )\n}\n\nMyApp.getInitialProps = async (context: NextAppContext) => {\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</p>\n</details>\n\n<details>\n<summary>Page: hooks/index.tsx</summary>\n<p>\n\n```tsx\nimport { useStore, getState } from '../index'\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button\n        onClick={e => {\n          actions.increment(33)\n        }}\n      >\n    </div>\n  )\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Single Page Config: benchmark.tsx</summary>\n<p>\n\n```tsx\n// ...\nBenchmark.getInitialProps = async () => {\n  return await getInitialState({ modelName: 'Todo' }, { isServer: true })\n}\n```\n</p>\n</details>\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<{}> = async (context, restMiddlewares) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: any) => 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) => async (params: any) => {\n  const context: Context = {\n    modelName,\n    setState,\n    actionName: action.name,\n    next: () => {},\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<\n  { name: string }, // State Type\n  { ext: undefined }, // ActionParamsType\n  { name: string } // ExtContextType\n> = {\n  actions: {\n    // { state, action } => { state, action, [name] }\n    ext: (_, { name }) => {\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 => '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      <Provider>\n        <Counter />\n      </Provider>\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<details>\n<summary>Javascript decorator version</summary>\n<p>\n\n```jsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\n\nconst mapProps = ({ light, counter }) => ({\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      <>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(5)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n      </>\n    )\n  }\n}\n```\n\n</p>\n</details>\n\n<details>\n<summary>TypeScript Version</summary>\n<p>\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) => ({\n  lightStatus: light ? 'open' : 'close',\n  counter,\n  response\n})\n\ntype RType = ReturnType<typeof mapProps>\n\nclass TSCounter extends PureComponent<\n  { state: RType } & { actions: ActionType }\n> {\n  render() {\n    const { state, actions } = this.props\n    return (\n      <>\n        <div>TS Counter</div>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(3)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n        <button onClick={e => actions.get()}>Get Response</button>\n        <div>message: {JSON.stringify(state.response)}</div>\n      </>\n    )\n  }\n}\n\nexport default connect(\n  'Home',\n  mapProps\n)(TSCounter)\n```\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\n\n## FAQ\n\n### How can I disable the console debugger\n\nJust remove consoleDebugger middleware.\n\n```typescript\nimport { actionMiddlewares } from 'react-model'\n// Find the index of middleware\nconst consoleDebuggerMiddlewareIndex = actionMiddlewares.indexOf(\n  middlewares.consoleDebugger\n)\n// Remove it\nactionMiddlewares.splice(consoleDebuggerMiddlewareIndex, 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) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: Error) => 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) => {\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      <div className=\"App\">\n        <Counter />\n        <Counter />\n        <Counter />\n      </div>\n    )\n  }\n}\n```\n\n<details>\n<summary>Counter model</summary>\n<p>\n\n```ts\ninterface State {\n  count: number\n}\n\ninterface ActionParams {\n  increment: number\n}\n\nconst model: ModelType<State, ActionParams> = {\n  state: {\n    count: 0\n  },\n  actions: {\n    increment: payload => {\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 => (state.count += payload)\n      return state => {\n        state.count += payload\n      }\n    }\n  }\n}\n\n```\n\n</p>\n</details>\n\n<details>\n<summary>Counter.tsx</summary>\n<p>\n\n```tsx\n\nconst Counter = () => {\n  const [{ useStore }] = useState(() => Model(model))\n  const [state, actions] = useStore()\n  return (\n    <div>\n      <div>{state.count}</div>\n      <button onClick={() => actions.increment(3)}>Increment</button>\n    </div>\n  )\n}\n\nexport default Counter\n```\n\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\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 => {\n    // Not allowed\n    // return state => (state.count += payload)\n    return state => {\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) => {\n  await timeout(1000, {})\n  context.next(restMiddlewares)\n}\n\nconst nextCounterModel: ModelType<CounterState, NextCounterActionParams> = {\n  actions: {\n    add: num => {\n      return state => {\n        state.count += num\n      }\n    },\n    increment: async (num, { actions }) => {\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","readmeFilename":"README.md","gitHead":"58830a235103569aa052001144e1b807253db2c5","_id":"react-model@3.1.1-rc.0","_nodeVersion":"12.16.1","_npmVersion":"6.13.4","dist":{"integrity":"sha512-AwKFU/ZMXQ1fgX9irWQac5DMzNlMn5n2sjRrKiYQQP+Za44REFqRTscb6n42l2dsWlb6zaAngtwPXi54NpFntQ==","shasum":"0120e00c95d02d1ab0221250e05200f8435002fa","tarball":"https://registry.npmjs.org/react-model/-/react-model-3.1.1-rc.0.tgz","fileCount":21,"unpackedSize":81221,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJehE+bCRA9TVsSAnZWagAA000P/3dBW5hBjnCYccfj6BkF\noPNindDRP+A/Uim8kr10p7uOiVFWD2G/puFnFSNcxUZGsjZAAuPM7cCV9/+A\n5LSjWNykSd1n5VIovr/iBjPk68EpWap+2E5Z3FY8niZ+JmAp63gR+ihcHdxQ\nXplrMiO4Ka2SGkc9p6VSrRJ45ka3yp23PEeN/4Wzh0j55IXgDyBx6erbxAB1\nxRP0BGPUYvgdNqVdk+juootju0wFyi0aVuCq0Xkevl2gBpob+elRL4F6EuxQ\n8pg4gWp/jUnNBilgYIfCLidh+5EnV1wsGn//YLVR3210cDyDmsDIHaho8tZw\naoXauX6BLKVKMn/HZG3cc7mTVl23vhtX7jlMWKBL2q3BdiZ93frluiaLC/NJ\n+516OAohYbAscx7AoQvYIjgA9VSvEz6CAua1KMtlQis3mAa247gyPtOD79LL\n8Za5OM+9hp1k4QTMPYK6KHZs9lCnZtHB9WR8uVAKpk5HHiSeZ7PLV4ANmNSJ\ngR0gBiCy7Avkifaw9hD4Y0fIoU44ZG+aB3U/iA1tRmkkWKq2E690FK/s3Z6C\n7qGGCiAYQwcqrIQX/K/Q1cgBC/q289f++DRaTXH5Zlah3Yrf/xcKaawl/qUn\n8j3WDalrYNcb1MlWht1U9MZfKG0B6vYuoKDjQ8L1IEZn0RC+fNrMySZ6sLHe\n2rw9\r\n=0HfO\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQC1SlVHlUVWwaJ/gdne5pdfAe3p7N8B9u+OxfB1Y6GzWwIhAKQpjOLf0MsFlm81U7mmjEK9rmzFBRIeKAZoeD8J64J9"}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_3.1.1-rc.0_1585729434515_0.5401914635225757"},"_hasShrinkwrap":false},"3.1.1":{"name":"react-model","version":"3.1.1","description":"The State management library for React","main":"./dist/react-model.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^6.0.0"},"peerDependencies":{"react":">=16.3.0","react-dom":">=16.3.0"},"devDependencies":{"@commitlint/cli":"^8.1.0","@commitlint/config-conventional":"^8.1.0","@testing-library/react":"^10.0.1","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/jest":"^25.1.0","@types/node":"^13.1.0","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","husky":"^4.0.2","jest":"^24.1.0","microbundle":"^0.11.0","prettier":"^2.0.0","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^8.0.0","remark-lint":"^7.0.0","remark-preset-lint-recommended":"^4.0.0","ts-jest":"^25.1.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"gitHead":"a9665bb61f052a3eb640e2b9b6f36627dd828c20","_id":"react-model@3.1.1","_nodeVersion":"12.10.0","_npmVersion":"6.10.3","dist":{"integrity":"sha512-2h2jyBaSC3XlgmscB76P23gcHIdwa2+B+qsvGr4PyN2j5qleDtP/hE+qgAv701hiTq0d4DWodW8ewKcNgQ7LMA==","shasum":"cbf68681f92766105b235b7306f617b86f88bfc9","tarball":"https://registry.npmjs.org/react-model/-/react-model-3.1.1.tgz","fileCount":21,"unpackedSize":81359,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJewov0CRA9TVsSAnZWagAA3xsP/REpIiFHVyBDf6so/jkC\nmIRw4yT2JUIhi4VkYLOAzo1mjxhAQ+XU1ZrxM8vH01ZqIxcwTqO898sTBj7u\nCAFmeimC1oZeU7PSESqVTW1CpqlXP1XfkBtgvzGZQlzgEbjrrR6GYEnLZVRh\nJMUoMocEGCkQDfgOvuvjNRKNJFgdqlDeem8DfIcVtVoSA3tNH3Z5RH8nMvhp\nJD9Mw6cV0a8jJ6yJ5xiT/85lLThCtHQTOLk8HG6TuIVslEE03kg4Epn17Aoz\no/SUE423a+30A0basKYhMtW2KFhMMMe3BVIqPPal3ZZch0ZUUKDlXIDNFdqK\nVX/XVDjFAjuuZEOw+ydCP3kuZd27BV3HixX26bWVSsy5LRFSJM3gXNCsbPNT\n9tCDcqXVa6tfN5eIwXts9Q7LBSUgdKUMMoaC/i9eFyhEvSuXwxcbniSOMonm\nHTJVLhQjiSAZ1NQVgRr3MDn1iZdkBz5qfEqU7esikEJ6vZOtnnauWcm1JD6f\n6Fbps9mWNco9iYPuFr5tvF3AbKp8rBYKkFsvwFzPmcK4oP9iOHJqKGMU6FOk\nVTK852PR09Q5dSVTwtSExP99NSxxI8Y37wZ4gWLPKJF3t8Qa/+DKVDz0yFRL\nXjAhXwWKRu9nMzIMksv2/TJfiMwa2odvXWGAKQUPMpPpfj4Abih2h1W9MPmR\nj3J0\r\n=ee3n\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDkUSSAw/XnCoEl5v89bmQiZAo5aPExzevhzN4aPiGvJwIhAIwkfAuZD7X0e2qxxe3lBf6d+Q3i+s7vjXg7FDFzg9/N"}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_3.1.1_1589808115769_0.271535504514703"},"_hasShrinkwrap":false},"3.1.2":{"name":"react-model","version":"3.1.2","description":"The State management library for React","main":"./dist/react-model.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^6.0.0"},"peerDependencies":{"react":">=16.3.0","react-dom":">=16.3.0"},"devDependencies":{"@commitlint/cli":"^8.1.0","@commitlint/config-conventional":"^8.1.0","@testing-library/react":"^10.0.1","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/jest":"^25.1.0","@types/node":"^14.0.0","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","husky":"^4.0.2","jest":"^24.1.0","microbundle":"^0.11.0","prettier":"^2.0.0","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^8.0.0","remark-lint":"^7.0.0","remark-preset-lint-recommended":"^4.0.0","ts-jest":"^25.1.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"gitHead":"4c071c2072ea06854c62ecea7bbb82c8c08d9e5e","_id":"react-model@3.1.2","_nodeVersion":"12.10.0","_npmVersion":"6.10.3","dist":{"integrity":"sha512-PawTcHsp7UYsWLVYFYSLXXdTkvUKVcxHgJm5m1b9nFEyfVPhGVsKxl1WdO86Bzitq9LXOFsMfncK17G5lr9uvQ==","shasum":"2ae5529dae23f060814d8e0ce745346048550aae","tarball":"https://registry.npmjs.org/react-model/-/react-model-3.1.2.tgz","fileCount":21,"unpackedSize":83114,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJew89xCRA9TVsSAnZWagAAc6YQAJ59wLL87i9TggLl7viJ\nVNf0EVgu0osKubLz5hv61QErvlFa2E1cfV6p0iJ/jXiJm8dng9xH0FfM40DQ\nLdsVifY5OT09ZzzwIrClTm7ajuBTWja72DC4AqYRBNMkZq5WGGGlMJZnLPz3\nfYFw+4sZZFJ6ZrjviUAbW2QiYaqfLSw0MczzYeWiEgrnsev2Eysyb7asERWE\nvCtG2NLD6YUxcZv6Et0w7aDX5vh3DNiosjzL/qz1XpzKR01+718g+mCtaMXp\n0O4WkUhdcCblzocAXoUGwGNaiH3jObHh7yoIL51ijBmRL1xnhXQ6kVcUblxC\nWc1DGxu52faybtVZnSakciE/jDk07xFI2H+R1TYq/8Xlzn6y1WAEtVuEKDhA\nXGtN0OMGFAll1AB1rKsq4qdYB1F2OjQ2AJW01UlMVeSjppEqfUb5rh1x9rb7\nRZ1aGQHo53BGEauRM/34Cz843xrXV7R9FdPDacADnLq9KCVaCwxix5zgQprp\nlZkCNmT1zzJeGMp89y0nNIxguhp05SZwfHUwWKO2nQ5uLnmOJ8UQ8nx3kKjp\n9JkRrVSrCQrCOSOe5ozZBwhqKTAtUhvR3uRxKB28+qCc6ARzJTZWzN7J0eg2\n8ZE/saQfnASX1JO2k9v47whJzM8JeJ5qYN0Zff4gaMLSM2NVrLl7NniRB1Be\n3dot\r\n=6A1P\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIFMhbsOhcs/c4yOkGjl9BjN4hhPN7oYWlz34Lbiywtm7AiEAxfCM7uErf95dm1NY/PjhS3AsPVHGxEzuy8eZHYrKeeg="}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_3.1.2_1589890928566_0.7527433113153101"},"_hasShrinkwrap":false},"4.0.0-rc.0":{"name":"react-model","version":"4.0.0-rc.0","description":"The State management library for React","main":"./dist/react-model.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^6.0.0"},"peerDependencies":{"react":">=16.3.0","react-dom":">=16.3.0"},"devDependencies":{"@commitlint/cli":"^8.1.0","@commitlint/config-conventional":"^8.1.0","@testing-library/react":"^10.0.1","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/jest":"^25.1.0","@types/node":"^14.0.0","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","husky":"^4.0.2","jest":"^24.1.0","microbundle":"^0.11.0","prettier":"^2.0.0","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^8.0.0","remark-lint":"^7.0.0","remark-preset-lint-recommended":"^4.0.0","ts-jest":"^25.1.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"readme":"# react-model &middot; ![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) [![Build Status](https://travis-ci.org/byte-fe/react-model.svg?branch=master)](https://travis-ci.org/byte-fe/react-model) [![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⚔ Fully TypeScript Support\n\n📦 Built with microbundle\n\n⚙️ Middlewares Pipline ( redux-devtools support ... )\n\n☂️ 100% test coverage, safe on production\n\n🐛 Debug easily on test environment\n\n```tsx\nimport { Model } from 'react-model'\n\n// define model\nconst Todo = {\n  state: {\n    items: ['Install react-model', 'Read github docs', 'Build App']\n  },\n  actions: {\n    add: todo => {\n      // s is the readonly version of state\n      // you can also return partial state here but don't need to keep immutable manually\n      // state is the mutable state\n      return state => {\n        state.items.push(todo)\n      }\n    }\n  }\n}\n\n// Model Register\nconst { useStore } = Model(Todo)\n\nconst App = () => {\n  return <TodoList />\n}\n\nconst TodoList = () => {\n  const [state, actions] = useStore()\n  return <div>\n    <Addon handler={actions.add} />\n    {state.items.map((item, index) => (<Todo key={index} item={item} />))}\n  </div>\n}\n```\n\n---\n\n## Recently Updated\n\n* [SSR Support: only return asyncState from server side](#ssr-with-nextjs)\n* [Expand Context](#expand-context)\n\n## Quick Start\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  - [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  - [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  - [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### Model\n\nEvery model have their 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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (payload, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    },\n    openLight: async (_, { state, actions }) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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(model)\n\n// You can use these types when use Class Components.\n// type ConsumerActionsType = getConsumerActionsType<typeof Model.actions>\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 keep the state and actions in the separate private store. 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 return from useStore can invoke the 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 () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button onClick={e => actions.increment(33)}>home increment</button>\n      <button onClick={e => sharedActions.increment(20)}>\n        shared increment\n      </button>\n      <button onClick={e => actions.get()}>fake request</button>\n      <button onClick={e => actions.openLight()}>fake nested call</button>\n    </div>\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 with depActions and React.memo to prevent child components rerender frequently.](https://github.com/ArrayZoneYour/react-model-todomvc/blob/master/src/components/TodoItem.tsx)\n\n[Demo Repo](https://github.com/ArrayZoneYour/react-model-todomvc)\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> Hint: The state returned should only be used as readonly\n\n```jsx\nimport { useStore, getState } from '../model/index'\n\nconst BasicHook = () => {\n  const [state, actions] = useStore('Counter')\n  useEffect(() => {\n    console.log('some mounted actions from BasicHooks')\n    return () =>\n      console.log(\n        `Basic Hooks unmounted, current Counter state: ${JSON.stringify(\n          getState('Counter')\n        )}`\n      )\n  }, [])\n  return (\n    <>\n      <div>state: {JSON.stringify(state)}</div>\n    </>\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: () => {\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 = () => {\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (params, { state: s }) => {\n      // issue: https://github.com/Microsoft/TypeScript/issues/29196\n      // async function return produce need define type manually.\n      return (state: typeof s) => {\n        state.counter += params || 1\n      }\n    },\n    decrease: params => s => {\n      s.counter += params || 1\n    }\n  }\n}\n\nexport default Model(model)\n```\n\nJavaScript Example\n\n```js\nconst Model = {\n  actions: {\n    increment: async (params) => {\n      return state => {\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<details>\n<summary>Store: shared.ts</summary>\n<p>\n\n```ts\nconst initialState = {\n  counter: 0\n}\n\nconst model: ModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: (params, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    }\n  },\n  // Provide for SSR\n  asyncState: async context => {\n    await waitFor(4000)\n    return { counter: 500 }\n  },\n  state: initialState\n}\n\nexport default Model(model)\n```\n\n</p>\n</details>\n\n<details>\n<summary>Global Config: _app.tsx</summary>\n<p>\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) => {\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    <Container>\n      <Component {...pageProps} />\n    </Container>\n  )\n}\n\nMyApp.getInitialProps = async (context: NextAppContext) => {\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</p>\n</details>\n\n<details>\n<summary>Page: hooks/index.tsx</summary>\n<p>\n\n```tsx\nimport { useStore, getState } from '../index'\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button\n        onClick={e => {\n          actions.increment(33)\n        }}\n      >\n    </div>\n  )\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Single Page Config: benchmark.tsx</summary>\n<p>\n\n```tsx\n// ...\nBenchmark.getInitialProps = async () => {\n  return await getInitialState({ modelName: 'Todo' }, { isServer: true })\n}\n```\n</p>\n</details>\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<{}> = async (context, restMiddlewares) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: any) => 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) => async (params: any) => {\n  const context: Context = {\n    modelName,\n    setState,\n    actionName: action.name,\n    next: () => {},\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<\n  { name: string }, // State Type\n  { ext: undefined }, // ActionParamsType\n  { name: string } // ExtContextType\n> = {\n  actions: {\n    // { state, action } => { state, action, [name] }\n    ext: (_, { name }) => {\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 => '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      <Provider>\n        <Counter />\n      </Provider>\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<details>\n<summary>Javascript decorator version</summary>\n<p>\n\n```jsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\n\nconst mapProps = ({ light, counter }) => ({\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      <>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(5)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n      </>\n    )\n  }\n}\n```\n\n</p>\n</details>\n\n<details>\n<summary>TypeScript Version</summary>\n<p>\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) => ({\n  lightStatus: light ? 'open' : 'close',\n  counter,\n  response\n})\n\ntype RType = ReturnType<typeof mapProps>\n\nclass TSCounter extends PureComponent<\n  { state: RType } & { actions: ActionType }\n> {\n  render() {\n    const { state, actions } = this.props\n    return (\n      <>\n        <div>TS Counter</div>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(3)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n        <button onClick={e => actions.get()}>Get Response</button>\n        <div>message: {JSON.stringify(state.response)}</div>\n      </>\n    )\n  }\n}\n\nexport default connect(\n  'Home',\n  mapProps\n)(TSCounter)\n```\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\n\n## FAQ\n\n### How can I disable the console debugger\n\nJust remove consoleDebugger middleware.\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 }) => ['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) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: Error) => 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) => {\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      <div className=\"App\">\n        <Counter />\n        <Counter />\n        <Counter />\n      </div>\n    )\n  }\n}\n```\n\n<details>\n<summary>Counter model</summary>\n<p>\n\n```ts\ninterface State {\n  count: number\n}\n\ninterface ActionParams {\n  increment: number\n}\n\nconst model: ModelType<State, ActionParams> = {\n  state: {\n    count: 0\n  },\n  actions: {\n    increment: payload => {\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 => (state.count += payload)\n      return state => {\n        state.count += payload\n      }\n    }\n  }\n}\n\n```\n\n</p>\n</details>\n\n<details>\n<summary>Counter.tsx</summary>\n<p>\n\n```tsx\n\nconst Counter = () => {\n  const [{ useStore }] = useState(() => Model(model))\n  const [state, actions] = useStore()\n  return (\n    <div>\n      <div>{state.count}</div>\n      <button onClick={() => actions.increment(3)}>Increment</button>\n    </div>\n  )\n}\n\nexport default Counter\n```\n\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\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 => {\n    // Not allowed\n    // return state => (state.count += payload)\n    return state => {\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) => {\n  await timeout(1000, {})\n  context.next(restMiddlewares)\n}\n\nconst nextCounterModel: ModelType<CounterState, NextCounterActionParams> = {\n  actions: {\n    add: num => {\n      return state => {\n        state.count += num\n      }\n    },\n    increment: async (num, { actions }) => {\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","readmeFilename":"README.md","gitHead":"5e195deb687c47890bb514daf81dcc48e60fa2bc","_id":"react-model@4.0.0-rc.0","_nodeVersion":"12.10.0","_npmVersion":"6.10.3","dist":{"integrity":"sha512-fSjHtWhN6DEsEYmgzB4UfIHeIS/rC273yv6Me+zmVNCOVxlSilG8x41VmxHTgDXGIl/dgKqOaE6Q5XqxHvhT1Q==","shasum":"b264bec86d9f71172d76feef58cd1d3c84af552e","tarball":"https://registry.npmjs.org/react-model/-/react-model-4.0.0-rc.0.tgz","fileCount":21,"unpackedSize":85872,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJew9GhCRA9TVsSAnZWagAAtNkP/3NrE6VwvS0ffgAqvkJk\n9ybtZDnTPfMDXUkq6XaJseiUnTTTis1Z6yn/N7VumQ9RVpuoUfTUbR5gZNA5\nQOdA2Oqvns1NXMCqfr13csOQjA1J6zTwl6nsMHEwYKDaxMmo4VYOuQInrW7r\nIIv04csNoEQPJPmc609GkNzDhPuKuiyadLE4Dbw8PjPpIHst/kdCMGRQM0U/\n3jLNJOSuMqoSiN3MIy4JsRDgzxI7jasLoJwiErysniJC1Dt7U2PyQ8BMyCSz\ndCiej1BI21ZFShTCVdU2Z4Ecyne6pLzxja7AWtXVPM1g6rRYLo4zvcnODuB8\nGGLiGy3Uq5FwoZIqmPOL7iMFQp+In4ge4Pig2F7I/PDdmEHUZzNA1bm2KFfM\nqhkWBqGCcXJHgMX3qzUjL0m6Y39ygryzKk6A/iwzgvUO2lmPZBHc542R1dag\nDdCFCU+OqHGJZmNhA/oKKPcTUCU2v5/L2wRZ++WQhxw2wj+3Vk1CZ9ODgUrj\n9/nbC/m30as9zD+i8u6MqMVm3PJ9k2LqY4fUITjTuq4j/BZK3c3ZPGKkYkGJ\nB4KpqrIelNKqezJo4b+7rmyG1fK9r/pCZaXgkEleQa/5d4X/GHxjuKRY2Ohu\nJDHvIfGUCUG3HCO1DYPMhSVQvZs2B+pO77CCIOE0WZSagLm8uWhvNjCmMXJ2\nUwjB\r\n=YXO4\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDKPugZCc2x+BBK7ILxswWmvKRTD903bfsO0lY04vaapwIgFKGlEIc4AOYCOeoBpk6T8TSoHfRDGMe7oGSCQQs4v3E="}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_4.0.0-rc.0_1589891489308_0.1640042432020754"},"_hasShrinkwrap":false},"4.0.0-dev.20200615.0":{"name":"react-model","version":"4.0.0-dev.20200615.0","description":"The State management library for React","main":"./dist/react-model.js","module":"./dist/react-model.mjs","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^6.0.0"},"peerDependencies":{"react":">=16.3.0","react-dom":">=16.3.0"},"devDependencies":{"@commitlint/cli":"^8.1.0","@commitlint/config-conventional":"^8.1.0","@testing-library/react":"^10.0.1","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/jest":"^25.1.0","@types/node":"^14.0.0","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","husky":"^4.0.2","jest":"^24.1.0","microbundle":"^0.12.0","prettier":"^2.0.0","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^8.0.0","remark-lint":"^7.0.0","remark-preset-lint-recommended":"^4.0.0","ts-jest":"^26.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"readme":"# react-model &middot; ![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) [![Build Status](https://travis-ci.org/byte-fe/react-model.svg?branch=master)](https://travis-ci.org/byte-fe/react-model) [![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 { Model } from 'react-model'\n\n// define model\nconst Todo = {\n  state: {\n    items: ['Install react-model', 'Read github docs', 'Build App']\n  },\n  actions: {\n    add: todo => {\n      // s is the readonly version of state\n      // you can also return partial state here but don't need to keep immutable manually\n      // state is the mutable state\n      return state => {\n        state.items.push(todo)\n      }\n    }\n  }\n}\n\n// Model Register\nconst { useStore } = Model(Todo)\n\nconst App = () => {\n  return <TodoList />\n}\n\nconst TodoList = () => {\n  const [state, actions] = useStore()\n  return <div>\n    <Addon handler={actions.add} />\n    {state.items.map((item, index) => (<Todo key={index} item={item} />))}\n  </div>\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[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  - [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  - [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  - [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### 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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (payload, { state }) => {\n      return {\n        counter: state.counter + (payload || 1)\n      }\n    },\n    openLight: async (_, { state, actions }) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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(model)\n\n// You can use these types when use Class Components.\n// type ConsumerActionsType = getConsumerActionsType<typeof Model.actions>\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 () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button onClick={e => actions.increment(33)}>home increment</button>\n      <button onClick={e => sharedActions.increment(20)}>\n        shared increment\n      </button>\n      <button onClick={e => actions.get()}>fake request</button>\n      <button onClick={e => actions.openLight()}>fake nested call</button>\n    </div>\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 >= 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> Hint: The state returned should only be used as readonly\n\n```jsx\nimport { useStore, getState } from '../model/index'\n\nconst BasicHook = () => {\n  const [state, actions] = useStore('Counter')\n  useEffect(() => {\n    console.log('some mounted actions from BasicHooks')\n    return () =>\n      console.log(\n        `Basic Hooks unmounted, current Counter state: ${JSON.stringify(\n          getState('Counter')\n        )}`\n      )\n  }, [])\n  return (\n    <>\n      <div>state: {JSON.stringify(state)}</div>\n    </>\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: () => {\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 = () => {\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (params, { state: s }) => {\n      // return (state: typeof s) => { // TypeScript < 3.9\n      return state => {\n        state.counter += params || 1\n      }\n    },\n    decrease: params => s => {\n      s.counter += params || 1\n    }\n  }\n}\n\nexport default Model(model)\n```\n\nJavaScript Example\n\n```js\nconst Model = {\n  actions: {\n    increment: async (params) => {\n      return state => {\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<details>\n<summary>Store: shared.ts</summary>\n<p>\n\n```ts\nconst initialState = {\n  counter: 0\n}\n\nconst model: ModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: (params, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    }\n  },\n  // Provide for SSR\n  asyncState: async context => {\n    await waitFor(4000)\n    return { counter: 500 }\n  },\n  state: initialState\n}\n\nexport default Model(model)\n```\n\n</p>\n</details>\n\n<details>\n<summary>Global Config: _app.tsx</summary>\n<p>\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) => {\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    <Container>\n      <Component {...pageProps} />\n    </Container>\n  )\n}\n\nMyApp.getInitialProps = async (context: NextAppContext) => {\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</p>\n</details>\n\n<details>\n<summary>Page: hooks/index.tsx</summary>\n<p>\n\n```tsx\nimport { useStore, getState } from '../index'\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button\n        onClick={e => {\n          actions.increment(33)\n        }}\n      >\n    </div>\n  )\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Single Page Config: benchmark.tsx</summary>\n<p>\n\n```tsx\n// ...\nBenchmark.getInitialProps = async () => {\n  return await getInitialState({ modelName: 'Todo' }, { isServer: true })\n}\n```\n</p>\n</details>\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<{}> = async (context, restMiddlewares) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: any) => 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) => async (params: any) => {\n  const context: Context = {\n    modelName,\n    setState,\n    actionName: action.name,\n    next: () => {},\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<\n  { name: string }, // State Type\n  { ext: undefined }, // ActionParamsType\n  { name: string } // ExtContextType\n> = {\n  actions: {\n    // { state, action } => { state, action, [name] }\n    ext: (_, { name }) => {\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 => '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      <Provider>\n        <Counter />\n      </Provider>\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<details>\n<summary>Javascript decorator version</summary>\n<p>\n\n```jsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\n\nconst mapProps = ({ light, counter }) => ({\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      <>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(5)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n      </>\n    )\n  }\n}\n```\n\n</p>\n</details>\n\n<details>\n<summary>TypeScript Version</summary>\n<p>\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) => ({\n  lightStatus: light ? 'open' : 'close',\n  counter,\n  response\n})\n\ntype RType = ReturnType<typeof mapProps>\n\nclass TSCounter extends PureComponent<\n  { state: RType } & { actions: ActionType }\n> {\n  render() {\n    const { state, actions } = this.props\n    return (\n      <>\n        <div>TS Counter</div>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(3)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n        <button onClick={e => actions.get()}>Get Response</button>\n        <div>message: {JSON.stringify(state.response)}</div>\n      </>\n    )\n  }\n}\n\nexport default connect(\n  'Home',\n  mapProps\n)(TSCounter)\n```\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\n\n## FAQ\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 }) => ['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) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: Error) => 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) => {\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      <div className=\"App\">\n        <Counter />\n        <Counter />\n        <Counter />\n      </div>\n    )\n  }\n}\n```\n\n<details>\n<summary>Counter model</summary>\n<p>\n\n```ts\ninterface State {\n  count: number\n}\n\ninterface ActionParams {\n  increment: number\n}\n\nconst model: ModelType<State, ActionParams> = {\n  state: {\n    count: 0\n  },\n  actions: {\n    increment: payload => {\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 => (state.count += payload)\n      return state => {\n        state.count += payload\n      }\n    }\n  }\n}\n\n```\n\n</p>\n</details>\n\n<details>\n<summary>Counter.tsx</summary>\n<p>\n\n```tsx\n\nconst Counter = () => {\n  const [{ useStore }] = useState(() => Model(model))\n  const [state, actions] = useStore()\n  return (\n    <div>\n      <div>{state.count}</div>\n      <button onClick={() => actions.increment(3)}>Increment</button>\n    </div>\n  )\n}\n\nexport default Counter\n```\n\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\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 => {\n    // Not allowed\n    // return state => (state.count += payload)\n    return state => {\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) => {\n  await timeout(1000, {})\n  context.next(restMiddlewares)\n}\n\nconst nextCounterModel: ModelType<CounterState, NextCounterActionParams> = {\n  actions: {\n    add: num => {\n      return state => {\n        state.count += num\n      }\n    },\n    increment: async (num, { actions }) => {\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","readmeFilename":"README.md","gitHead":"ce789a5b96ecd7c4d71e701473fe9280dd1fb954","_id":"react-model@4.0.0-dev.20200615.0","_nodeVersion":"12.10.0","_npmVersion":"6.10.3","dist":{"integrity":"sha512-BlcZ1MG+zoY9GE2UquGoqrgDKn2Iv36MLrNsF0BX/iK4/pjx3aaXv+SkaK1EXv04JHg1zg3KlI4jxsOmvaR1pA==","shasum":"3466b2319109ca2d368407dd97b518ba062789e7","tarball":"https://registry.npmjs.org/react-model/-/react-model-4.0.0-dev.20200615.0.tgz","fileCount":21,"unpackedSize":85748,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJe5y6GCRA9TVsSAnZWagAASXAP/Ar/vT5t70kdkUMOYtAi\nxC2uirDQ3G7O7nv4SEgFFp5FiNtaEaie4UnSf4RbZS/zAjM2MqC3uUQtROEo\nUObVbHI82pSJQQsZWsXlrHwoBSATj/cINcsHkJh2UtFxlIsmk4r2NwL5rbZt\nPMANAku1ttTpNy4PV9Vq4ARuUe45KTgxuR6+DvWlUzBGQNoRys+nbZG55EPD\nZXgQO1q4FUAza08zg6AiztR3Fe7YCGEtV8cOeUn4SmGMixS3w+OJztQaLJHa\nFxscUny/0nx0+/qDZcCS0t5cXnfh/fauvZtS/xtSAg8r1QyUT2IdQjnI3GUC\n+zbTq6QTUFUNpqgJQeT6KS+SwqYdGkJGqQgu331ut2pC86sjTrTk7IGUpD4L\ngeR3Qf5qXb65ktyUppV1ihM/rY09vKMAh2W2+X9LYbWK0Vxs4JiQplKT41y7\naYOV+4Y57Jr67kIfmXNFdWXsI6Evymo7bi6OA8SJqAS47gr9SuegmzLn1Il5\nBGLxPYSGxnd8u1BPBShAXmizis+0vgxupzxpoja6Phfl5RrAOzIBpkiTHHFg\nGHVjeGzmZTcDabQY7HR0qLuZnsw8IWMLGmAkUPZqao+HujA2c2fhU28ADhCl\nPzr8IMFkuNHSlrE7Fb61veU9wxLxzU22mFUWxYXo4ctLUz+Mkd0/7Ev7TKof\n4IiL\r\n=zb3I\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDcm52rfEw0MCswNn/acdPRwN6CWV9GWNL8J59DA2eLDgIgcMFWvBukEZZZduz9B5hTT4ROcluU9C7tS9x2WxNQlq8="}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_4.0.0-dev.20200615.0_1592209029730_0.798330733183465"},"_hasShrinkwrap":false},"4.0.0-dev.20200615.1":{"name":"react-model","version":"4.0.0-dev.20200615.1","description":"The State management library for React","main":"./dist/react-model.js","module":"./dist/react-model.mjs","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","sideEffects":false,"dependencies":{"immer":"^6.0.0"},"peerDependencies":{"react":">=16.3.0","react-dom":">=16.3.0"},"devDependencies":{"@commitlint/cli":"^8.1.0","@commitlint/config-conventional":"^8.1.0","@testing-library/react":"^10.0.1","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/jest":"^25.1.0","@types/node":"^14.0.0","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","husky":"^4.0.2","jest":"^24.1.0","microbundle":"^0.12.0","prettier":"^2.0.0","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^8.0.0","remark-lint":"^7.0.0","remark-preset-lint-recommended":"^4.0.0","ts-jest":"^26.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"readme":"# react-model &middot; ![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) [![Build Status](https://travis-ci.org/byte-fe/react-model.svg?branch=master)](https://travis-ci.org/byte-fe/react-model) [![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 { Model } from 'react-model'\n\n// define model\nconst Todo = {\n  state: {\n    items: ['Install react-model', 'Read github docs', 'Build App']\n  },\n  actions: {\n    add: todo => {\n      // s is the readonly version of state\n      // you can also return partial state here but don't need to keep immutable manually\n      // state is the mutable state\n      return state => {\n        state.items.push(todo)\n      }\n    }\n  }\n}\n\n// Model Register\nconst { useStore } = Model(Todo)\n\nconst App = () => {\n  return <TodoList />\n}\n\nconst TodoList = () => {\n  const [state, actions] = useStore()\n  return <div>\n    <Addon handler={actions.add} />\n    {state.items.map((item, index) => (<Todo key={index} item={item} />))}\n  </div>\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[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  - [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  - [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  - [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### 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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (payload, { state }) => {\n      return {\n        counter: state.counter + (payload || 1)\n      }\n    },\n    openLight: async (_, { state, actions }) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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(model)\n\n// You can use these types when use Class Components.\n// type ConsumerActionsType = getConsumerActionsType<typeof Model.actions>\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 () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button onClick={e => actions.increment(33)}>home increment</button>\n      <button onClick={e => sharedActions.increment(20)}>\n        shared increment\n      </button>\n      <button onClick={e => actions.get()}>fake request</button>\n      <button onClick={e => actions.openLight()}>fake nested call</button>\n    </div>\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 >= 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> Hint: The state returned should only be used as readonly\n\n```jsx\nimport { useStore, getState } from '../model/index'\n\nconst BasicHook = () => {\n  const [state, actions] = useStore('Counter')\n  useEffect(() => {\n    console.log('some mounted actions from BasicHooks')\n    return () =>\n      console.log(\n        `Basic Hooks unmounted, current Counter state: ${JSON.stringify(\n          getState('Counter')\n        )}`\n      )\n  }, [])\n  return (\n    <>\n      <div>state: {JSON.stringify(state)}</div>\n    </>\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: () => {\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 = () => {\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (params, { state: s }) => {\n      // return (state: typeof s) => { // TypeScript < 3.9\n      return state => {\n        state.counter += params || 1\n      }\n    },\n    decrease: params => s => {\n      s.counter += params || 1\n    }\n  }\n}\n\nexport default Model(model)\n```\n\nJavaScript Example\n\n```js\nconst Model = {\n  actions: {\n    increment: async (params) => {\n      return state => {\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<details>\n<summary>Store: shared.ts</summary>\n<p>\n\n```ts\nconst initialState = {\n  counter: 0\n}\n\nconst model: ModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: (params, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    }\n  },\n  // Provide for SSR\n  asyncState: async context => {\n    await waitFor(4000)\n    return { counter: 500 }\n  },\n  state: initialState\n}\n\nexport default Model(model)\n```\n\n</p>\n</details>\n\n<details>\n<summary>Global Config: _app.tsx</summary>\n<p>\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) => {\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    <Container>\n      <Component {...pageProps} />\n    </Container>\n  )\n}\n\nMyApp.getInitialProps = async (context: NextAppContext) => {\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</p>\n</details>\n\n<details>\n<summary>Page: hooks/index.tsx</summary>\n<p>\n\n```tsx\nimport { useStore, getState } from '../index'\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button\n        onClick={e => {\n          actions.increment(33)\n        }}\n      >\n    </div>\n  )\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Single Page Config: benchmark.tsx</summary>\n<p>\n\n```tsx\n// ...\nBenchmark.getInitialProps = async () => {\n  return await getInitialState({ modelName: 'Todo' }, { isServer: true })\n}\n```\n</p>\n</details>\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<{}> = async (context, restMiddlewares) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: any) => 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) => async (params: any) => {\n  const context: Context = {\n    modelName,\n    setState,\n    actionName: action.name,\n    next: () => {},\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<\n  { name: string }, // State Type\n  { ext: undefined }, // ActionParamsType\n  { name: string } // ExtContextType\n> = {\n  actions: {\n    // { state, action } => { state, action, [name] }\n    ext: (_, { name }) => {\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 => '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      <Provider>\n        <Counter />\n      </Provider>\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<details>\n<summary>Javascript decorator version</summary>\n<p>\n\n```jsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\n\nconst mapProps = ({ light, counter }) => ({\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      <>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(5)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n      </>\n    )\n  }\n}\n```\n\n</p>\n</details>\n\n<details>\n<summary>TypeScript Version</summary>\n<p>\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) => ({\n  lightStatus: light ? 'open' : 'close',\n  counter,\n  response\n})\n\ntype RType = ReturnType<typeof mapProps>\n\nclass TSCounter extends PureComponent<\n  { state: RType } & { actions: ActionType }\n> {\n  render() {\n    const { state, actions } = this.props\n    return (\n      <>\n        <div>TS Counter</div>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(3)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n        <button onClick={e => actions.get()}>Get Response</button>\n        <div>message: {JSON.stringify(state.response)}</div>\n      </>\n    )\n  }\n}\n\nexport default connect(\n  'Home',\n  mapProps\n)(TSCounter)\n```\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\n\n## FAQ\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 }) => ['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) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: Error) => 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) => {\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      <div className=\"App\">\n        <Counter />\n        <Counter />\n        <Counter />\n      </div>\n    )\n  }\n}\n```\n\n<details>\n<summary>Counter model</summary>\n<p>\n\n```ts\ninterface State {\n  count: number\n}\n\ninterface ActionParams {\n  increment: number\n}\n\nconst model: ModelType<State, ActionParams> = {\n  state: {\n    count: 0\n  },\n  actions: {\n    increment: payload => {\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 => (state.count += payload)\n      return state => {\n        state.count += payload\n      }\n    }\n  }\n}\n\n```\n\n</p>\n</details>\n\n<details>\n<summary>Counter.tsx</summary>\n<p>\n\n```tsx\n\nconst Counter = () => {\n  const [{ useStore }] = useState(() => Model(model))\n  const [state, actions] = useStore()\n  return (\n    <div>\n      <div>{state.count}</div>\n      <button onClick={() => actions.increment(3)}>Increment</button>\n    </div>\n  )\n}\n\nexport default Counter\n```\n\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\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 => {\n    // Not allowed\n    // return state => (state.count += payload)\n    return state => {\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) => {\n  await timeout(1000, {})\n  context.next(restMiddlewares)\n}\n\nconst nextCounterModel: ModelType<CounterState, NextCounterActionParams> = {\n  actions: {\n    add: num => {\n      return state => {\n        state.count += num\n      }\n    },\n    increment: async (num, { actions }) => {\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","readmeFilename":"README.md","gitHead":"ce789a5b96ecd7c4d71e701473fe9280dd1fb954","_id":"react-model@4.0.0-dev.20200615.1","_nodeVersion":"12.10.0","_npmVersion":"6.10.3","dist":{"integrity":"sha512-i/VgFoHYjHmIYLR2QBx0lLAgTbZzqsuO4I8h3HhZHRfq7gtdTYh373MIsEnPaZEimaVciadf9YUT/zy/bnTImw==","shasum":"02c115705834b3eef9aa61f15ef004bf0aed1da0","tarball":"https://registry.npmjs.org/react-model/-/react-model-4.0.0-dev.20200615.1.tgz","fileCount":21,"unpackedSize":85772,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJe5zNZCRA9TVsSAnZWagAAuIAP/3XO+vu71fV66pUZ/Edv\nNdbcKg08vzo74wMmCgBKdrMeioaeIdn+fscoLufhxFj4uq80koDtFr3gbbU5\niaBDh76NxH+lNEVhhUcQEpFO/XTKm7ryELcDv4V5fdwUCtKoCqhFn6aXgoON\nygpB98IdsCfmMY9W9LLHPPTwOrnIvj5gaTkj0e/A0/AIKoleIi1/ZOXCZGHN\nRzLPd/2Id9JNNTAOxjYrFBBKIUIVwYD+ypapW/rtNoTiUDSCkzk9jg98UkDS\ns90dSSdMw7Hm7MnSzma+Q6LEszIPpJTTlRQECq7WP5oL4fEwRANt1Jo+aNDw\na7vtNErMAQI7ndaYrYpr+Vo6FVU0aVYVZxRQ9m7z1nK3utuldnsosh+q/7Sc\npEMaaHpklW+SNBm2eRaQEeJAyaFD3aHQaio3/LDZ4XeD+5kp6YTA48d/Btu5\nWZNGH5pddAjVkSsRB1khnpvEFGOX8ry1Udp7kqQbl90cxDWPDT2E6oiKe/L0\nQwhTyUQvCkId7T1IT2jH4Fm+U3l5RXu23ZIUAo7Duz7UEGC405DXUg2cEcij\n5XOId9hF6zQN0Jc5Z4E+EADI7xAOmuMupNAEk8Rwyfay6pWZeaG75gerDJqh\nsI2dmSqsFFUbZ8SctboF+9ORfL/2tX9a4YccbT6NRP/AiIwD35BjSoL4Ty4U\noMO8\r\n=UJ8M\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDgBHTzYtX94BK7nyi1oMs/VZoQJE8mi4EkWtRuwCvm9wIgVPNP1+UcvBfVkKDyg9wJt+8FSf4A0jTviuBOULt91Cc="}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_4.0.0-dev.20200615.1_1592210265518_0.3899793843764958"},"_hasShrinkwrap":false},"3.1.3":{"name":"react-model","version":"3.1.3","description":"The State management library for React","main":"./dist/react-model.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^6.0.0"},"peerDependencies":{"react":">=16.3.0","react-dom":">=16.3.0"},"devDependencies":{"@commitlint/cli":"^8.1.0","@commitlint/config-conventional":"^8.1.0","@testing-library/react":"^10.0.1","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/jest":"^25.1.0","@types/node":"^14.0.0","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","husky":"^4.0.2","jest":"^24.1.0","microbundle":"^0.11.0","prettier":"^2.0.0","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^8.0.0","remark-lint":"^7.0.0","remark-preset-lint-recommended":"^4.0.0","ts-jest":"^25.1.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"gitHead":"0a3904e129d6f17705f43c49877d5ae2b4161910","_id":"react-model@3.1.3","_nodeVersion":"12.10.0","_npmVersion":"6.10.3","dist":{"integrity":"sha512-Oy9z3Kf08gCdvKJhkhsOxBJtvA/1pPG/Kbi/nTqCEBc0zJv1Nfa34qVfBsnkcOmfjB7b88DPgbP3z18Ho9Isjg==","shasum":"94fce248db1d8bd644e592a0e5b0ce621c3c6fd9","tarball":"https://registry.npmjs.org/react-model/-/react-model-3.1.3.tgz","fileCount":21,"unpackedSize":83180,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfBXBJCRA9TVsSAnZWagAAlS4P/ilFo3Gxxdlrz8WYMMUz\ny3Z8iGcEJ8Dth5l4m00h+WyhPS1WP+GTIxJB4WJcBinNTQd9kgO0DjMIK19E\nuQPNiuCZI27is3cuk7KBi7THOPvi4JHv6w+rXXiA9lF90+AIkoIjhxUK+6P8\nSgppsfq5mO+SCzo+4D0E2UflAJZewSEqPCj3wgfq4FT/DG/qcHlAxECquJ2H\nA/LNd3s3xv4OfQ0JHODeM2hsUDobY9Qa/AJMnpMKmw2wgIbhzCr/07sve358\nEIHwmfspwaM9osCyIxu/LiVcEl/llslJsi3Fu+40UPavbRyp5GBmBknF3POy\nohqYMDbiOn0ixo/qzCpCTSPm6lSsXAbhtTbPx/hxpNSPrgfL/juOJPjzCUWL\n1OHe8yQNpTV44klIBMnoN5pEStIj2/J9MlzHpysmzorTXux9SLxhox/Hpao0\npRyg97Ezq+X+QM85f8hPU+NntD79+2q81rzIjAIosPYC6Xq6WWtD98Zn9zMg\nW+5d3U4mCm7gGHJOwNNDRAyblVhPDbFGo1fez+/P/zcCcdBVOSM51V6sR8b0\nbYBM5ma2iOew7JyYeIGIgTMD8utYO5KbfIiUlVid0JwmI9ypQ/9BweRB8Rw5\nULaHUDihj9kxVMhbv4HbkJwDfujrKZAuErMg8EJmUnvekRSMHNhR/BgSHZjT\nWzir\r\n=xuvJ\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDnFURWFr/hc4NaojsmJkwGpS8FlpzXQmQjcpsCGYljCAIgGmtQefuzYJh3mHkh9NvgzJXM/5PMjGadd8QCx9C/ZsE="}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_3.1.3_1594191945023_0.5746050155398807"},"_hasShrinkwrap":false},"3.1.4":{"name":"react-model","version":"3.1.4","description":"The State management library for React","main":"./dist/react-model.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build":"microbundle -o ./dist --sourcemap false","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^6.0.0"},"peerDependencies":{"react":">=16.3.0","react-dom":">=16.3.0"},"devDependencies":{"@commitlint/cli":"^8.1.0","@commitlint/config-conventional":"^8.1.0","@testing-library/react":"^10.0.1","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/jest":"^25.1.0","@types/node":"^14.0.0","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","husky":"^4.0.2","jest":"^24.1.0","microbundle":"^0.11.0","prettier":"^2.0.0","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^8.0.0","remark-lint":"^7.0.0","remark-preset-lint-recommended":"^4.0.0","ts-jest":"^25.1.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"gitHead":"160b2407301fe0ddf872819cf7da9bdda6c21df4","_id":"react-model@3.1.4","_nodeVersion":"12.10.0","_npmVersion":"6.10.3","dist":{"integrity":"sha512-xo61DoSWOiNe4ldq8a3GTy7GC2cDQGLgDSePqfvsE99xDQ8q+hNrspxM70xT8PM0PcJ4hBA2V8zMuGciA1IOEw==","shasum":"04b52931298880776670fe7fad9cb19d7ba183b3","tarball":"https://registry.npmjs.org/react-model/-/react-model-3.1.4.tgz","fileCount":21,"unpackedSize":84192,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfBZ3ICRA9TVsSAnZWagAAy9YQAItXZsFnjCclcFgCX7WS\nPXDBI0jmqAwujCHGDPyu2RDyCEpbEBDx6k+RKauH9sIWjtJfBHMOLg7b7hp2\nZnz/dZ8HveEDEoAdAMIScvQrDReuE8vTsVNJHPLRGNTpop6yZRsAGRFNNDhF\nGAD6EykqkrkAZR3X9O5gYCnixjxvCNo51G8F+O3vOLDysPyp5PXXVOLRtPb1\naP3C0+W4eObhgN4DcmmVsr3OYTyoweS0f7jTC+QVIYRuZxgAXfdeop6RPCK2\n8XWHwAYVcOREeDZGU8+fZq2A4tqWjpO3n80+6w5USlPdJkQzdz7sCwmE++YH\nKRDAkeZWIa3UO5Q1B5CecIZyI9aiHkStv2OtF59t/m5xnguELj44uuVA/foo\niqxa/63pkxGS6n2UeWfbQVvybf8hLGzcO1ABng9zO4NQruWeCd3CxKyWuuLo\nbn4gWZX18inJyrIZ0YG3fcv/v2035tO9nkdzcTPFPhqWqSxkPaFOm0pHXNbr\nEDefAqE6loSobQFZmc4cw4sOjbvych3sGFuq+28roU305nja3mK06F8RNh7G\nhfrf/KID9iOzzTF9kDVS23fqyBQ2vz4/8yaCGwwDWYO7G049IepX/PY0Nn0Y\nMxBgiCgKUt+z6/LThecZLlSg9QkZ5k/UrS7o2/YHrR8mgW/ruXjLAwYtB5PJ\ndnbx\r\n=FUiJ\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIG0GD5Q3k+g/z4XIH0+XqUb2iEZ4rFIXTZSMbZM2YiIAAiEAhMWxw881B0Y+LU/KdQb/JfczIlDAw3H6N9Nd1W+hA3A="}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_3.1.4_1594203591408_0.22538553662014804"},"_hasShrinkwrap":false},"4.0.0-rc.1":{"name":"react-model","version":"4.0.0-rc.1","description":"The State management library for React","main":"./dist/react-model.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build":"microbundle --sourcemap false --jsx React.createElement","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^6.0.0"},"peerDependencies":{"react":">=16.3.0","react-dom":">=16.3.0"},"devDependencies":{"@commitlint/cli":"^8.1.0","@commitlint/config-conventional":"^8.1.0","@testing-library/react":"^10.0.1","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/jest":"^25.1.0","@types/node":"^14.0.0","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","husky":"^4.0.2","jest":"^24.1.0","microbundle":"^0.12.3","prettier":"^2.0.0","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^8.0.0","remark-lint":"^7.0.0","remark-preset-lint-recommended":"^4.0.0","ts-jest":"^26.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"readme":"# react-model &middot; ![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) [![Build Status](https://travis-ci.org/byte-fe/react-model.svg?branch=master)](https://travis-ci.org/byte-fe/react-model) [![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 { Model } from 'react-model'\n\n// define model\nconst Todo = {\n  state: {\n    items: ['Install react-model', 'Read github docs', 'Build App']\n  },\n  actions: {\n    add: todo => {\n      // s is the readonly version of state\n      // you can also return partial state here but don't need to keep immutable manually\n      // state is the mutable state\n      return state => {\n        state.items.push(todo)\n      }\n    }\n  }\n}\n\n// Model Register\nconst { useStore } = Model(Todo)\n\nconst App = () => {\n  return <TodoList />\n}\n\nconst TodoList = () => {\n  const [state, actions] = useStore()\n  return <div>\n    <Addon handler={actions.add} />\n    {state.items.map((item, index) => (<Todo key={index} item={item} />))}\n  </div>\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[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  - [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  - [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  - [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### 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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (payload, { state }) => {\n      return {\n        counter: state.counter + (payload || 1)\n      }\n    },\n    openLight: async (_, { state, actions }) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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(model)\n\n// You can use these types when use Class Components.\n// type ConsumerActionsType = getConsumerActionsType<typeof Model.actions>\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 () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button onClick={e => actions.increment(33)}>home increment</button>\n      <button onClick={e => sharedActions.increment(20)}>\n        shared increment\n      </button>\n      <button onClick={e => actions.get()}>fake request</button>\n      <button onClick={e => actions.openLight()}>fake nested call</button>\n    </div>\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 >= 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> Hint: The state returned should only be used as readonly\n\n```jsx\nimport { useStore, getState } from '../model/index'\n\nconst BasicHook = () => {\n  const [state, actions] = useStore('Counter')\n  useEffect(() => {\n    console.log('some mounted actions from BasicHooks')\n    return () =>\n      console.log(\n        `Basic Hooks unmounted, current Counter state: ${JSON.stringify(\n          getState('Counter')\n        )}`\n      )\n  }, [])\n  return (\n    <>\n      <div>state: {JSON.stringify(state)}</div>\n    </>\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: () => {\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 = () => {\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (params, { state: s }) => {\n      // return (state: typeof s) => { // TypeScript < 3.9\n      return state => {\n        state.counter += params || 1\n      }\n    },\n    decrease: params => s => {\n      s.counter += params || 1\n    }\n  }\n}\n\nexport default Model(model)\n```\n\nJavaScript Example\n\n```js\nconst Model = {\n  actions: {\n    increment: async (params) => {\n      return state => {\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<details>\n<summary>Store: shared.ts</summary>\n<p>\n\n```ts\nconst initialState = {\n  counter: 0\n}\n\nconst model: ModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: (params, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    }\n  },\n  // Provide for SSR\n  asyncState: async context => {\n    await waitFor(4000)\n    return { counter: 500 }\n  },\n  state: initialState\n}\n\nexport default Model(model)\n```\n\n</p>\n</details>\n\n<details>\n<summary>Global Config: _app.tsx</summary>\n<p>\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) => {\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    <Container>\n      <Component {...pageProps} />\n    </Container>\n  )\n}\n\nMyApp.getInitialProps = async (context: NextAppContext) => {\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</p>\n</details>\n\n<details>\n<summary>Page: hooks/index.tsx</summary>\n<p>\n\n```tsx\nimport { useStore, getState } from '../index'\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button\n        onClick={e => {\n          actions.increment(33)\n        }}\n      >\n    </div>\n  )\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Single Page Config: benchmark.tsx</summary>\n<p>\n\n```tsx\n// ...\nBenchmark.getInitialProps = async () => {\n  return await getInitialState({ modelName: 'Todo' }, { isServer: true })\n}\n```\n</p>\n</details>\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<{}> = async (context, restMiddlewares) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: any) => 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) => async (params: any) => {\n  const context: Context = {\n    modelName,\n    setState,\n    actionName: action.name,\n    next: () => {},\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<\n  { name: string }, // State Type\n  { ext: undefined }, // ActionParamsType\n  { name: string } // ExtContextType\n> = {\n  actions: {\n    // { state, action } => { state, action, [name] }\n    ext: (_, { name }) => {\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 => '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      <Provider>\n        <Counter />\n      </Provider>\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<details>\n<summary>Javascript decorator version</summary>\n<p>\n\n```jsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\n\nconst mapProps = ({ light, counter }) => ({\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      <>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(5)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n      </>\n    )\n  }\n}\n```\n\n</p>\n</details>\n\n<details>\n<summary>TypeScript Version</summary>\n<p>\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) => ({\n  lightStatus: light ? 'open' : 'close',\n  counter,\n  response\n})\n\ntype RType = ReturnType<typeof mapProps>\n\nclass TSCounter extends PureComponent<\n  { state: RType } & { actions: ActionType }\n> {\n  render() {\n    const { state, actions } = this.props\n    return (\n      <>\n        <div>TS Counter</div>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(3)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n        <button onClick={e => actions.get()}>Get Response</button>\n        <div>message: {JSON.stringify(state.response)}</div>\n      </>\n    )\n  }\n}\n\nexport default connect(\n  'Home',\n  mapProps\n)(TSCounter)\n```\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\n\n## FAQ\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 }) => ['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) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: Error) => 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) => {\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      <div className=\"App\">\n        <Counter />\n        <Counter />\n        <Counter />\n      </div>\n    )\n  }\n}\n```\n\n<details>\n<summary>Counter model</summary>\n<p>\n\n```ts\ninterface State {\n  count: number\n}\n\ninterface ActionParams {\n  increment: number\n}\n\nconst model: ModelType<State, ActionParams> = {\n  state: {\n    count: 0\n  },\n  actions: {\n    increment: payload => {\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 => (state.count += payload)\n      return state => {\n        state.count += payload\n      }\n    }\n  }\n}\n\n```\n\n</p>\n</details>\n\n<details>\n<summary>Counter.tsx</summary>\n<p>\n\n```tsx\n\nconst Counter = () => {\n  const [{ useStore }] = useState(() => Model(model))\n  const [state, actions] = useStore()\n  return (\n    <div>\n      <div>{state.count}</div>\n      <button onClick={() => actions.increment(3)}>Increment</button>\n    </div>\n  )\n}\n\nexport default Counter\n```\n\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\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 => {\n    // Not allowed\n    // return state => (state.count += payload)\n    return state => {\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) => {\n  await timeout(1000, {})\n  context.next(restMiddlewares)\n}\n\nconst nextCounterModel: ModelType<CounterState, NextCounterActionParams> = {\n  actions: {\n    add: num => {\n      return state => {\n        state.count += num\n      }\n    },\n    increment: async (num, { actions }) => {\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","readmeFilename":"README.md","gitHead":"3f94f3a59ce1b6ecdf1011ce1aebe1dbd9883b1e","_id":"react-model@4.0.0-rc.1","_nodeVersion":"12.10.0","_npmVersion":"6.10.3","dist":{"integrity":"sha512-BmhXlMr/+N4uKF+ylfb+FtDdWQ24Nn3L10yZe1h1QnBOPBzGoSxJV/Cu7tDFUHcLTMppsUgUxqpnvtIDNBg2Ng==","shasum":"4f1b91c88b9ff368cc44f7decf46bd186b905051","tarball":"https://registry.npmjs.org/react-model/-/react-model-4.0.0-rc.1.tgz","fileCount":22,"unpackedSize":94289,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfIPbWCRA9TVsSAnZWagAAlN4QAJTu2iFn22AoROAQp4MH\nk42dD8N+mX7I6c0ornLhJIWAdmJdl+jA4KoChQwGjJkXt8xCNg5lWG0FkhIz\nThGqHecyV3kdtdvNO4XUCnld1/RyczhpXvV+R7BFhtU1oyUIM03wfTk/830W\nXDN+YBUhiCcntSd1Fzk0NaW8vBYMNALuQf/EjJB62n8rVynNcKmjrHHzH92Z\n4l/x7hhvr6O3AwKRn7xMyljEwck46RoFOvMdX0k8OKstgVWcKbUrlrzbccF3\n6fwmmZSPBIIsO9hrL3cbvIFB8QJDB1pUynNgvtimQcxJYAcKxhbaGo61L5K6\nXv2E/Fci9TuyYGolCjDDw2zA0xEFTDABhRwXNCczAbD1vQ6LDUw2xJuEjIOP\nwrRUTghzxG28VgHm7JAeuhRdAbHaYQZRI+qajHivvLefWwX93gPBVWYjt5P/\nxQs7sOu+gR/ix5kqTk7x5EdCckcNVAzmVtsoMaFjZy0P5H8eD1cv5ZfKriQG\njnYijJg94IVax6bRXTeohrsvnpsk0o5ayy3DFJOIibPQWhgoIdZ1jighNOPP\nlSx4EwioDdo2Nxv5LRQiTRiPDFdR+/cnH7qxQcycU1S9iBvVmcXRoWnzyNTO\n9zxa1B+9vUJf7DLwgvlOUJ4ICbNnQdP3JtnsB1dWyBe4jcbbYjfhLjuqXxsF\n1rwj\r\n=gvs5\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCICEf1cDwqe/lda2hnzRDgbMrXme6kw6nY2PXZHIoL76gAiB/5MJIxWHv/xFkpHdHgTFSPW1jSTD6MT2M8INPzgB96A=="}]},"maintainers":[{"email":"hustliyidong@gmail.com","name":"arrayzoneyour"},{"email":"leecade@163.com","name":"leecade"}],"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_4.0.0-rc.1_1595995862359_0.9984141430820499"},"_hasShrinkwrap":false},"4.0.0-rc.2":{"name":"react-model","version":"4.0.0-rc.2","description":"The State management library for React","main":"./dist/react-model.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build":"microbundle --sourcemap false --jsx React.createElement --output dist --tsconfig ./tsconfig.json","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^6.0.0"},"peerDependencies":{"react":">=16.3.0","react-dom":">=16.3.0"},"devDependencies":{"@commitlint/cli":"^8.1.0","@commitlint/config-conventional":"^8.1.0","@testing-library/react":"^10.0.1","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/jest":"^25.1.0","@types/node":"^14.0.0","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","husky":"^4.0.2","jest":"^24.1.0","microbundle":"^0.12.3","prettier":"^2.0.0","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^8.0.0","remark-lint":"^7.0.0","remark-preset-lint-recommended":"^4.0.0","ts-jest":"^26.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"readme":"# react-model &middot; ![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) [![Build Status](https://travis-ci.org/byte-fe/react-model.svg?branch=master)](https://travis-ci.org/byte-fe/react-model) [![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 { Model } from 'react-model'\n\n// define model\nconst Todo = {\n  state: {\n    items: ['Install react-model', 'Read github docs', 'Build App']\n  },\n  actions: {\n    add: todo => {\n      // s is the readonly version of state\n      // you can also return partial state here but don't need to keep immutable manually\n      // state is the mutable state\n      return state => {\n        state.items.push(todo)\n      }\n    }\n  }\n}\n\n// Model Register\nconst { useStore } = Model(Todo)\n\nconst App = () => {\n  return <TodoList />\n}\n\nconst TodoList = () => {\n  const [state, actions] = useStore()\n  return <div>\n    <Addon handler={actions.add} />\n    {state.items.map((item, index) => (<Todo key={index} item={item} />))}\n  </div>\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[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  - [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  - [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  - [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### 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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (payload, { state }) => {\n      return {\n        counter: state.counter + (payload || 1)\n      }\n    },\n    openLight: async (_, { state, actions }) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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(model)\n\n// You can use these types when use Class Components.\n// type ConsumerActionsType = getConsumerActionsType<typeof Model.actions>\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 () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button onClick={e => actions.increment(33)}>home increment</button>\n      <button onClick={e => sharedActions.increment(20)}>\n        shared increment\n      </button>\n      <button onClick={e => actions.get()}>fake request</button>\n      <button onClick={e => actions.openLight()}>fake nested call</button>\n    </div>\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 >= 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> Hint: The state returned should only be used as readonly\n\n```jsx\nimport { useStore, getState } from '../model/index'\n\nconst BasicHook = () => {\n  const [state, actions] = useStore('Counter')\n  useEffect(() => {\n    console.log('some mounted actions from BasicHooks')\n    return () =>\n      console.log(\n        `Basic Hooks unmounted, current Counter state: ${JSON.stringify(\n          getState('Counter')\n        )}`\n      )\n  }, [])\n  return (\n    <>\n      <div>state: {JSON.stringify(state)}</div>\n    </>\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: () => {\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 = () => {\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (params, { state: s }) => {\n      // return (state: typeof s) => { // TypeScript < 3.9\n      return state => {\n        state.counter += params || 1\n      }\n    },\n    decrease: params => s => {\n      s.counter += params || 1\n    }\n  }\n}\n\nexport default Model(model)\n```\n\nJavaScript Example\n\n```js\nconst Model = {\n  actions: {\n    increment: async (params) => {\n      return state => {\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<details>\n<summary>Store: shared.ts</summary>\n<p>\n\n```ts\nconst initialState = {\n  counter: 0\n}\n\nconst model: ModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: (params, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    }\n  },\n  // Provide for SSR\n  asyncState: async context => {\n    await waitFor(4000)\n    return { counter: 500 }\n  },\n  state: initialState\n}\n\nexport default Model(model)\n```\n\n</p>\n</details>\n\n<details>\n<summary>Global Config: _app.tsx</summary>\n<p>\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) => {\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    <Container>\n      <Component {...pageProps} />\n    </Container>\n  )\n}\n\nMyApp.getInitialProps = async (context: NextAppContext) => {\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</p>\n</details>\n\n<details>\n<summary>Page: hooks/index.tsx</summary>\n<p>\n\n```tsx\nimport { useStore, getState } from '../index'\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button\n        onClick={e => {\n          actions.increment(33)\n        }}\n      >\n    </div>\n  )\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Single Page Config: benchmark.tsx</summary>\n<p>\n\n```tsx\n// ...\nBenchmark.getInitialProps = async () => {\n  return await getInitialState({ modelName: 'Todo' }, { isServer: true })\n}\n```\n</p>\n</details>\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<{}> = async (context, restMiddlewares) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: any) => 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) => async (params: any) => {\n  const context: Context = {\n    modelName,\n    setState,\n    actionName: action.name,\n    next: () => {},\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<\n  { name: string }, // State Type\n  { ext: undefined }, // ActionParamsType\n  { name: string } // ExtContextType\n> = {\n  actions: {\n    // { state, action } => { state, action, [name] }\n    ext: (_, { name }) => {\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 => '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      <Provider>\n        <Counter />\n      </Provider>\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<details>\n<summary>Javascript decorator version</summary>\n<p>\n\n```jsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\n\nconst mapProps = ({ light, counter }) => ({\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      <>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(5)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n      </>\n    )\n  }\n}\n```\n\n</p>\n</details>\n\n<details>\n<summary>TypeScript Version</summary>\n<p>\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) => ({\n  lightStatus: light ? 'open' : 'close',\n  counter,\n  response\n})\n\ntype RType = ReturnType<typeof mapProps>\n\nclass TSCounter extends PureComponent<\n  { state: RType } & { actions: ActionType }\n> {\n  render() {\n    const { state, actions } = this.props\n    return (\n      <>\n        <div>TS Counter</div>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(3)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n        <button onClick={e => actions.get()}>Get Response</button>\n        <div>message: {JSON.stringify(state.response)}</div>\n      </>\n    )\n  }\n}\n\nexport default connect(\n  'Home',\n  mapProps\n)(TSCounter)\n```\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\n\n## FAQ\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 }) => ['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) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: Error) => 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) => {\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      <div className=\"App\">\n        <Counter />\n        <Counter />\n        <Counter />\n      </div>\n    )\n  }\n}\n```\n\n<details>\n<summary>Counter model</summary>\n<p>\n\n```ts\ninterface State {\n  count: number\n}\n\ninterface ActionParams {\n  increment: number\n}\n\nconst model: ModelType<State, ActionParams> = {\n  state: {\n    count: 0\n  },\n  actions: {\n    increment: payload => {\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 => (state.count += payload)\n      return state => {\n        state.count += payload\n      }\n    }\n  }\n}\n\n```\n\n</p>\n</details>\n\n<details>\n<summary>Counter.tsx</summary>\n<p>\n\n```tsx\n\nconst Counter = () => {\n  const [{ useStore }] = useState(() => Model(model))\n  const [state, actions] = useStore()\n  return (\n    <div>\n      <div>{state.count}</div>\n      <button onClick={() => actions.increment(3)}>Increment</button>\n    </div>\n  )\n}\n\nexport default Counter\n```\n\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\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 => {\n    // Not allowed\n    // return state => (state.count += payload)\n    return state => {\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) => {\n  await timeout(1000, {})\n  context.next(restMiddlewares)\n}\n\nconst nextCounterModel: ModelType<CounterState, NextCounterActionParams> = {\n  actions: {\n    add: num => {\n      return state => {\n        state.count += num\n      }\n    },\n    increment: async (num, { actions }) => {\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","readmeFilename":"README.md","gitHead":"403a7e2d8a50e846267a6036bc59fc8339e4ab21","_id":"react-model@4.0.0-rc.2","_nodeVersion":"14.0.0","_npmVersion":"6.14.4","dist":{"integrity":"sha512-exrm3UeefWrjs2ea8GFSlMnZ2WDG61c+KDwaHKj6f6wQpFgZBsOlzFj0y+vjvmX1UQDpUCKM2iE3+xpSKLxRhw==","shasum":"3068931cce77772c1413947a0bf778c8efe82f96","tarball":"https://registry.npmjs.org/react-model/-/react-model-4.0.0-rc.2.tgz","fileCount":22,"unpackedSize":94509,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfbD8rCRA9TVsSAnZWagAAX3QP/10mj3rxfypj3Mys8QPS\nckbXldiDGD59ct+rvM1T8Vdx14vvzbOfPHW4v2nfT0XSH0Z/QuDcymO91JJa\npvqM8FlNpkHtm/e1DXiyxLHHUSiCwVtNQ+Voyy1FCo1XFhpcdHVueIhiwfO0\nVmQ/7VOoGnBfSsEgwSMMXQp20WnwjUIUOpLMSwv+9cdf0shn03/Sf/orrYER\nu9T3CnzZPq7UydVz+i6vfIFLH3HVsScbPVXc6ZD13P2xPgHwEibAnX5jB9so\n4EI/pEH9A5mZMuCAnA+pnyNn8oI2F/84jkUfEksipy1YPvtwVEAlZB+00CNq\nzwKqFnec/hvtovvNYRKOY6+6Fa+XzftN0hMrxg6aIoDl0HF7lKuAlqDSJQT5\nKTM26AxMIyMcDv4oKANEnt/jC5bk8QH4bgerI0BlC5VjpLx8PrkRXjICrgzn\nDX/4OglPvai2anB+WSEaH7QNRUox/h6XGS//movc8dADubaTLRUHBsPj6r0G\n2nkI8YoDyFyJ7ZUuywUmKAHPZGxonGlBiGphm64d/AsPzz93eQ2pyxYVENak\n1pu9275T7jVdeIvMtVYbVvI1IjoK/mbvEQIsDNROnhlVbv2sbA/qKg8Nm0vG\n1mJ7YAWn9LwhDvd3XfPzCE6prnFCMdMLZxux8ANcAtkqybVhoGaZfvAxYj9Y\nyhFB\r\n=r6N7\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDqQxJ446W+siuB4SoP5YixCPCt4wl5AIjffNJCwAQZcgIge6ZZDiLIV6gteNZ/xZX+/NLQxXCZrb23Q9AXadlfoH4="}]},"maintainers":[{"name":"leecade","email":"leecade@163.com"},{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"}],"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_4.0.0-rc.2_1600929578738_0.5361460063228167"},"_hasShrinkwrap":false},"4.0.0-rc.3":{"name":"react-model","version":"4.0.0-rc.3","description":"The State management library for React","main":"./dist/react-model.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build":"microbundle --sourcemap false --jsx React.createElement --output dist --tsconfig ./tsconfig.json","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^6.0.0"},"peerDependencies":{"react":">=16.3.0","react-dom":">=16.3.0"},"devDependencies":{"@commitlint/cli":"^8.1.0","@commitlint/config-conventional":"^8.1.0","@testing-library/react":"^10.0.1","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/jest":"^25.1.0","@types/node":"^14.0.0","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","husky":"^4.0.2","jest":"^24.1.0","microbundle":"^0.12.3","prettier":"^2.0.0","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^8.0.0","remark-lint":"^7.0.0","remark-preset-lint-recommended":"^4.0.0","ts-jest":"^26.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"readme":"# react-model &middot; ![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) [![Build Status](https://travis-ci.org/byte-fe/react-model.svg?branch=master)](https://travis-ci.org/byte-fe/react-model) [![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 { Model } from 'react-model'\n\n// define model\nconst Todo = {\n  state: {\n    items: ['Install react-model', 'Read github docs', 'Build App']\n  },\n  actions: {\n    add: todo => {\n      // s is the readonly version of state\n      // you can also return partial state here but don't need to keep immutable manually\n      // state is the mutable state\n      return state => {\n        state.items.push(todo)\n      }\n    }\n  }\n}\n\n// Model Register\nconst { useStore } = Model(Todo)\n\nconst App = () => {\n  return <TodoList />\n}\n\nconst TodoList = () => {\n  const [state, actions] = useStore()\n  return <div>\n    <Addon handler={actions.add} />\n    {state.items.map((item, index) => (<Todo key={index} item={item} />))}\n  </div>\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[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  - [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  - [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  - [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### 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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (payload, { state }) => {\n      return {\n        counter: state.counter + (payload || 1)\n      }\n    },\n    openLight: async (_, { state, actions }) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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(model)\n\n// You can use these types when use Class Components.\n// type ConsumerActionsType = getConsumerActionsType<typeof Model.actions>\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 () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button onClick={e => actions.increment(33)}>home increment</button>\n      <button onClick={e => sharedActions.increment(20)}>\n        shared increment\n      </button>\n      <button onClick={e => actions.get()}>fake request</button>\n      <button onClick={e => actions.openLight()}>fake nested call</button>\n    </div>\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 >= 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> Hint: The state returned should only be used as readonly\n\n```jsx\nimport { useStore, getState } from '../model/index'\n\nconst BasicHook = () => {\n  const [state, actions] = useStore('Counter')\n  useEffect(() => {\n    console.log('some mounted actions from BasicHooks')\n    return () =>\n      console.log(\n        `Basic Hooks unmounted, current Counter state: ${JSON.stringify(\n          getState('Counter')\n        )}`\n      )\n  }, [])\n  return (\n    <>\n      <div>state: {JSON.stringify(state)}</div>\n    </>\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: () => {\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 = () => {\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (params, { state: s }) => {\n      // return (state: typeof s) => { // TypeScript < 3.9\n      return state => {\n        state.counter += params || 1\n      }\n    },\n    decrease: params => s => {\n      s.counter += params || 1\n    }\n  }\n}\n\nexport default Model(model)\n```\n\nJavaScript Example\n\n```js\nconst Model = {\n  actions: {\n    increment: async (params) => {\n      return state => {\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<details>\n<summary>Store: shared.ts</summary>\n<p>\n\n```ts\nconst initialState = {\n  counter: 0\n}\n\nconst model: ModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: (params, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    }\n  },\n  // Provide for SSR\n  asyncState: async context => {\n    await waitFor(4000)\n    return { counter: 500 }\n  },\n  state: initialState\n}\n\nexport default Model(model)\n```\n\n</p>\n</details>\n\n<details>\n<summary>Global Config: _app.tsx</summary>\n<p>\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) => {\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    <Container>\n      <Component {...pageProps} />\n    </Container>\n  )\n}\n\nMyApp.getInitialProps = async (context: NextAppContext) => {\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</p>\n</details>\n\n<details>\n<summary>Page: hooks/index.tsx</summary>\n<p>\n\n```tsx\nimport { useStore, getState } from '../index'\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button\n        onClick={e => {\n          actions.increment(33)\n        }}\n      >\n    </div>\n  )\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Single Page Config: benchmark.tsx</summary>\n<p>\n\n```tsx\n// ...\nBenchmark.getInitialProps = async () => {\n  return await getInitialState({ modelName: 'Todo' }, { isServer: true })\n}\n```\n</p>\n</details>\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<{}> = async (context, restMiddlewares) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: any) => 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) => async (params: any) => {\n  const context: Context = {\n    modelName,\n    setState,\n    actionName: action.name,\n    next: () => {},\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<\n  { name: string }, // State Type\n  { ext: undefined }, // ActionParamsType\n  { name: string } // ExtContextType\n> = {\n  actions: {\n    // { state, action } => { state, action, [name] }\n    ext: (_, { name }) => {\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 => '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      <Provider>\n        <Counter />\n      </Provider>\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<details>\n<summary>Javascript decorator version</summary>\n<p>\n\n```jsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\n\nconst mapProps = ({ light, counter }) => ({\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      <>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(5)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n      </>\n    )\n  }\n}\n```\n\n</p>\n</details>\n\n<details>\n<summary>TypeScript Version</summary>\n<p>\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) => ({\n  lightStatus: light ? 'open' : 'close',\n  counter,\n  response\n})\n\ntype RType = ReturnType<typeof mapProps>\n\nclass TSCounter extends PureComponent<\n  { state: RType } & { actions: ActionType }\n> {\n  render() {\n    const { state, actions } = this.props\n    return (\n      <>\n        <div>TS Counter</div>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(3)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n        <button onClick={e => actions.get()}>Get Response</button>\n        <div>message: {JSON.stringify(state.response)}</div>\n      </>\n    )\n  }\n}\n\nexport default connect(\n  'Home',\n  mapProps\n)(TSCounter)\n```\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\n\n## FAQ\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 }) => ['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) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: Error) => 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) => {\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      <div className=\"App\">\n        <Counter />\n        <Counter />\n        <Counter />\n      </div>\n    )\n  }\n}\n```\n\n<details>\n<summary>Counter model</summary>\n<p>\n\n```ts\ninterface State {\n  count: number\n}\n\ninterface ActionParams {\n  increment: number\n}\n\nconst model: ModelType<State, ActionParams> = {\n  state: {\n    count: 0\n  },\n  actions: {\n    increment: payload => {\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 => (state.count += payload)\n      return state => {\n        state.count += payload\n      }\n    }\n  }\n}\n\n```\n\n</p>\n</details>\n\n<details>\n<summary>Counter.tsx</summary>\n<p>\n\n```tsx\n\nconst Counter = () => {\n  const [{ useStore }] = useState(() => Model(model))\n  const [state, actions] = useStore()\n  return (\n    <div>\n      <div>{state.count}</div>\n      <button onClick={() => actions.increment(3)}>Increment</button>\n    </div>\n  )\n}\n\nexport default Counter\n```\n\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\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 => {\n    // Not allowed\n    // return state => (state.count += payload)\n    return state => {\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) => {\n  await timeout(1000, {})\n  context.next(restMiddlewares)\n}\n\nconst nextCounterModel: ModelType<CounterState, NextCounterActionParams> = {\n  actions: {\n    add: num => {\n      return state => {\n        state.count += num\n      }\n    },\n    increment: async (num, { actions }) => {\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","readmeFilename":"README.md","gitHead":"403a7e2d8a50e846267a6036bc59fc8339e4ab21","_id":"react-model@4.0.0-rc.3","_nodeVersion":"14.0.0","_npmVersion":"6.14.4","dist":{"integrity":"sha512-7aUHeU6RJxzNvnadim5rwPFULaioRjiZGLTsoIhFfsaiQC3SupnTEZsl/cWBaz6ECeHclv384avSjAAnFpM0iA==","shasum":"b9c7fa1bda311acd03eb8b5a3c473fc860c9c44d","tarball":"https://registry.npmjs.org/react-model/-/react-model-4.0.0-rc.3.tgz","fileCount":22,"unpackedSize":95695,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfbGQUCRA9TVsSAnZWagAARSsP/1oehUYn2z/J3F1hJ728\n8ugXtnLk+LqGg73kOUTLD/90hsMyFl7lQtM5JW/Quj6eBWJhVl3b1Z8jH2ds\nNixRheRfKaDDDJn4c5WezaAZW4eWHNrDEttuXTWuXM7zHnwdrbbHAjqNLVYe\ngUJS0vzW20ae2hhYKQzz0KCewAkLsYbZR2LmvJA3V6oN3Ptp+UoxyFGwZ0zY\nQYEZFEO4oR1wCPzoFFHmS+gIh2o0cHrmaU1INv2iG2kLuJK/6kbVeaWkLwky\nEr/weaxMc0YTfq1hRrRDtht1EdQ1x3tMgeUW24QqsJCvi3rSRTzwDbRNzH1z\n2wy6ytRdJUjnL7vzmu+zCylsRMvzgU91zA5I/MECz7/2JJusvTwV40kWl28+\ngWUi6uIN5B8i2zhfdrAstqOFAKdT/mwfqtNlKIITvXy3ZsUW82kb1HNu8/pT\nvXsYY2HDtJvqk/laeuqb/ECAhkLzrKkn8gicuU3wOyu9v/qgYS5Y79TdSg+7\nb7+Mwrp16uBsSDvvmnrPJc3btySTat9v8NGw0g7P5ECQqpK+54gJ30CEZvOB\nrvlioVZ3GSw2awkWPAr5PMI3T5IIZsPJS2tYFXIeHNOtvQEq4q445Ps+zkCa\nRGmudMmZbvxntI3VGerVLMnUv+yAR+RMdAX/QiWuLLfMegVrYNffHHEuJgNf\nBG2n\r\n=ZGXN\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIEBhUevr7cU6K1FCs/d1VSYyiuCziOY+cV/GHOLICsVtAiEAocMpylFH85Y2Cpx/B1ZK/5RALRdufVVcTNihkw7r8L8="}]},"maintainers":[{"name":"leecade","email":"leecade@163.com"},{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"}],"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_4.0.0-rc.3_1600939027530_0.9864439850737525"},"_hasShrinkwrap":false},"4.0.0-rc.4":{"name":"react-model","version":"4.0.0-rc.4","description":"The State management library for React","main":"./dist/react-model.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build":"microbundle --sourcemap false --jsx React.createElement --output dist --tsconfig ./tsconfig.json","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^6.0.0"},"peerDependencies":{"react":">=16.3.0","react-dom":">=16.3.0"},"devDependencies":{"@commitlint/cli":"^8.1.0","@commitlint/config-conventional":"^8.1.0","@testing-library/react":"^10.0.1","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/jest":"^25.1.0","@types/node":"^14.0.0","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","husky":"^4.0.2","jest":"^24.1.0","microbundle":"^0.12.3","prettier":"^2.0.0","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^8.0.0","remark-lint":"^7.0.0","remark-preset-lint-recommended":"^4.0.0","ts-jest":"^26.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"browserslist":["edge 17","firefox 70","chrome 48","safari 12.1","android 4.0","samsung 9.2"],"readme":"# react-model &middot; ![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) [![Build Status](https://travis-ci.org/byte-fe/react-model.svg?branch=master)](https://travis-ci.org/byte-fe/react-model) [![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 { Model } from 'react-model'\n\n// define model\nconst Todo = {\n  state: {\n    items: ['Install react-model', 'Read github docs', 'Build App']\n  },\n  actions: {\n    add: todo => {\n      // s is the readonly version of state\n      // you can also return partial state here but don't need to keep immutable manually\n      // state is the mutable state\n      return state => {\n        state.items.push(todo)\n      }\n    }\n  }\n}\n\n// Model Register\nconst { useStore } = Model(Todo)\n\nconst App = () => {\n  return <TodoList />\n}\n\nconst TodoList = () => {\n  const [state, actions] = useStore()\n  return <div>\n    <Addon handler={actions.add} />\n    {state.items.map((item, index) => (<Todo key={index} item={item} />))}\n  </div>\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[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  - [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  - [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  - [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### 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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (payload, { state }) => {\n      return {\n        counter: state.counter + (payload || 1)\n      }\n    },\n    openLight: async (_, { state, actions }) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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(model)\n\n// You can use these types when use Class Components.\n// type ConsumerActionsType = getConsumerActionsType<typeof Model.actions>\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 () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button onClick={e => actions.increment(33)}>home increment</button>\n      <button onClick={e => sharedActions.increment(20)}>\n        shared increment\n      </button>\n      <button onClick={e => actions.get()}>fake request</button>\n      <button onClick={e => actions.openLight()}>fake nested call</button>\n    </div>\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 >= 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> Hint: The state returned should only be used as readonly\n\n```jsx\nimport { useStore, getState } from '../model/index'\n\nconst BasicHook = () => {\n  const [state, actions] = useStore('Counter')\n  useEffect(() => {\n    console.log('some mounted actions from BasicHooks')\n    return () =>\n      console.log(\n        `Basic Hooks unmounted, current Counter state: ${JSON.stringify(\n          getState('Counter')\n        )}`\n      )\n  }, [])\n  return (\n    <>\n      <div>state: {JSON.stringify(state)}</div>\n    </>\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: () => {\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 = () => {\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (params, { state: s }) => {\n      // return (state: typeof s) => { // TypeScript < 3.9\n      return state => {\n        state.counter += params || 1\n      }\n    },\n    decrease: params => s => {\n      s.counter += params || 1\n    }\n  }\n}\n\nexport default Model(model)\n```\n\nJavaScript Example\n\n```js\nconst Model = {\n  actions: {\n    increment: async (params) => {\n      return state => {\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<details>\n<summary>Store: shared.ts</summary>\n<p>\n\n```ts\nconst initialState = {\n  counter: 0\n}\n\nconst model: ModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: (params, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    }\n  },\n  // Provide for SSR\n  asyncState: async context => {\n    await waitFor(4000)\n    return { counter: 500 }\n  },\n  state: initialState\n}\n\nexport default Model(model)\n```\n\n</p>\n</details>\n\n<details>\n<summary>Global Config: _app.tsx</summary>\n<p>\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) => {\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    <Container>\n      <Component {...pageProps} />\n    </Container>\n  )\n}\n\nMyApp.getInitialProps = async (context: NextAppContext) => {\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</p>\n</details>\n\n<details>\n<summary>Page: hooks/index.tsx</summary>\n<p>\n\n```tsx\nimport { useStore, getState } from '../index'\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button\n        onClick={e => {\n          actions.increment(33)\n        }}\n      >\n    </div>\n  )\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Single Page Config: benchmark.tsx</summary>\n<p>\n\n```tsx\n// ...\nBenchmark.getInitialProps = async () => {\n  return await getInitialState({ modelName: 'Todo' }, { isServer: true })\n}\n```\n</p>\n</details>\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<{}> = async (context, restMiddlewares) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: any) => 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) => async (params: any) => {\n  const context: Context = {\n    modelName,\n    setState,\n    actionName: action.name,\n    next: () => {},\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<\n  { name: string }, // State Type\n  { ext: undefined }, // ActionParamsType\n  { name: string } // ExtContextType\n> = {\n  actions: {\n    // { state, action } => { state, action, [name] }\n    ext: (_, { name }) => {\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 => '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      <Provider>\n        <Counter />\n      </Provider>\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<details>\n<summary>Javascript decorator version</summary>\n<p>\n\n```jsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\n\nconst mapProps = ({ light, counter }) => ({\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      <>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(5)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n      </>\n    )\n  }\n}\n```\n\n</p>\n</details>\n\n<details>\n<summary>TypeScript Version</summary>\n<p>\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) => ({\n  lightStatus: light ? 'open' : 'close',\n  counter,\n  response\n})\n\ntype RType = ReturnType<typeof mapProps>\n\nclass TSCounter extends PureComponent<\n  { state: RType } & { actions: ActionType }\n> {\n  render() {\n    const { state, actions } = this.props\n    return (\n      <>\n        <div>TS Counter</div>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(3)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n        <button onClick={e => actions.get()}>Get Response</button>\n        <div>message: {JSON.stringify(state.response)}</div>\n      </>\n    )\n  }\n}\n\nexport default connect(\n  'Home',\n  mapProps\n)(TSCounter)\n```\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\n\n## FAQ\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 }) => ['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) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: Error) => 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) => {\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      <div className=\"App\">\n        <Counter />\n        <Counter />\n        <Counter />\n      </div>\n    )\n  }\n}\n```\n\n<details>\n<summary>Counter model</summary>\n<p>\n\n```ts\ninterface State {\n  count: number\n}\n\ninterface ActionParams {\n  increment: number\n}\n\nconst model: ModelType<State, ActionParams> = {\n  state: {\n    count: 0\n  },\n  actions: {\n    increment: payload => {\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 => (state.count += payload)\n      return state => {\n        state.count += payload\n      }\n    }\n  }\n}\n\n```\n\n</p>\n</details>\n\n<details>\n<summary>Counter.tsx</summary>\n<p>\n\n```tsx\n\nconst Counter = () => {\n  const [{ useStore }] = useState(() => Model(model))\n  const [state, actions] = useStore()\n  return (\n    <div>\n      <div>{state.count}</div>\n      <button onClick={() => actions.increment(3)}>Increment</button>\n    </div>\n  )\n}\n\nexport default Counter\n```\n\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\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 => {\n    // Not allowed\n    // return state => (state.count += payload)\n    return state => {\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) => {\n  await timeout(1000, {})\n  context.next(restMiddlewares)\n}\n\nconst nextCounterModel: ModelType<CounterState, NextCounterActionParams> = {\n  actions: {\n    add: num => {\n      return state => {\n        state.count += num\n      }\n    },\n    increment: async (num, { actions }) => {\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","readmeFilename":"README.md","gitHead":"403a7e2d8a50e846267a6036bc59fc8339e4ab21","_id":"react-model@4.0.0-rc.4","_nodeVersion":"14.0.0","_npmVersion":"6.14.4","dist":{"integrity":"sha512-c5lKgr8xH6ZveWDSkc9wRdLHB32gPK2ybFBXZ4EOLswLSGPuOl7Z5bdP0ZtmMU4QfD3q6lBNQXKvCxinWjFy1g==","shasum":"86c475308e1c1803b4f228f92c4adbda37192526","tarball":"https://registry.npmjs.org/react-model/-/react-model-4.0.0-rc.4.tgz","fileCount":22,"unpackedSize":95803,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfcw2yCRA9TVsSAnZWagAA4bsP/2t0RAkqhpfw3QVS/Pj2\nZqlqLWDffi1O5IUar7wSGYh8xeEh1nrHAwkodEiW0F7F6MF36Gvsz6kSfNXp\no2MYCVPFECCpwT/oNVrS/s1s1/7N8sf5jX9bxc7d2iU/oFo+BL//Fd196d7h\nKWRTNEORERTKNLTt5HRaYMiLpxGIrXKjVc97q18oimFCGb0U3q8Hg4qFo2d7\nKbBZCUX90h89MkeIcNigeEyOgRfREpMmG1+q4NtNQbT3zCwEuOBzLYQ8pTAT\nu6U5EUQeSN5Y0DViq79raMUOFzVy8FtWrnt/1jd+vCyP6ZxyuYRExFMtY/5l\nDFiyk21RTGKFFw1mHXZYiCGGLbWds+b8mKWGZvZygf564kQ2M8+7jO8rMtsj\nAUybUEyFB8Lb24Ic1dZ8V5+ioB6SjA6VHL8m5pETfaG0zw5s3BgyxaHXeFQg\nm0R2C73RKEvEGxspr/iZXipyjPhA3Da8qGrblnzpJ9XLWsmg9hnndjKplF+R\ngCxvYbSBL2YecScN051wbiO93wFPwCfxXfnwMAe7eV651w147ifFgj+NK2U5\nYtm7/PqkvSro44WXdN5FJpSIb6RuRidY3bxgs6+lG8jcSbX8ldgNlC3SCJ9r\nXkQj1knPjD2lZYIxtrhBUxdUGDGV4gUDwEsAdIHZqqAxuNKVwJL/J4SzxLCA\nAjwn\r\n=3naY\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCID5QF63UFOz74SpTHWGvYUo/J7vIacCiEtYcympBriK0AiAF+eYQCYR5oMoPPCPWvn5TG7SbLIBN8JTjpBVG+xlY4Q=="}]},"maintainers":[{"name":"leecade","email":"leecade@163.com"},{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"}],"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_4.0.0-rc.4_1601375666074_0.1460446193138818"},"_hasShrinkwrap":false},"4.0.0-rc.5":{"name":"react-model","version":"4.0.0-rc.5","description":"The State management library for React","main":"./dist/react-model.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build":"microbundle --sourcemap false --jsx React.createElement --output dist --tsconfig ./tsconfig.json","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^6.0.0"},"peerDependencies":{"react":">=16.3.0","react-dom":">=16.3.0"},"devDependencies":{"@commitlint/cli":"^8.1.0","@commitlint/config-conventional":"^8.1.0","@testing-library/react":"^10.0.1","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/jest":"^25.1.0","@types/node":"^14.0.0","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","husky":"^4.0.2","jest":"^24.1.0","microbundle":"^0.12.3","prettier":"^2.0.0","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^8.0.0","remark-lint":"^7.0.0","remark-preset-lint-recommended":"^4.0.0","ts-jest":"^26.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"browserslist":["edge 17","firefox 70","chrome 48","safari 12.1","android 4.0","samsung 9.2"],"readme":"# react-model &middot; ![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) [![Build Status](https://travis-ci.org/byte-fe/react-model.svg?branch=master)](https://travis-ci.org/byte-fe/react-model) [![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 { Model } from 'react-model'\n\n// define model\nconst Todo = {\n  state: {\n    items: ['Install react-model', 'Read github docs', 'Build App']\n  },\n  actions: {\n    add: todo => {\n      // s is the readonly version of state\n      // you can also return partial state here but don't need to keep immutable manually\n      // state is the mutable state\n      return state => {\n        state.items.push(todo)\n      }\n    }\n  }\n}\n\n// Model Register\nconst { useStore } = Model(Todo)\n\nconst App = () => {\n  return <TodoList />\n}\n\nconst TodoList = () => {\n  const [state, actions] = useStore()\n  return <div>\n    <Addon handler={actions.add} />\n    {state.items.map((item, index) => (<Todo key={index} item={item} />))}\n  </div>\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[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  - [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  - [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  - [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### 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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (payload, { state }) => {\n      return {\n        counter: state.counter + (payload || 1)\n      }\n    },\n    openLight: async (_, { state, actions }) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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(model)\n\n// You can use these types when use Class Components.\n// type ConsumerActionsType = getConsumerActionsType<typeof Model.actions>\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 () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button onClick={e => actions.increment(33)}>home increment</button>\n      <button onClick={e => sharedActions.increment(20)}>\n        shared increment\n      </button>\n      <button onClick={e => actions.get()}>fake request</button>\n      <button onClick={e => actions.openLight()}>fake nested call</button>\n    </div>\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 >= 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> Hint: The state returned should only be used as readonly\n\n```jsx\nimport { useStore, getState } from '../model/index'\n\nconst BasicHook = () => {\n  const [state, actions] = useStore('Counter')\n  useEffect(() => {\n    console.log('some mounted actions from BasicHooks')\n    return () =>\n      console.log(\n        `Basic Hooks unmounted, current Counter state: ${JSON.stringify(\n          getState('Counter')\n        )}`\n      )\n  }, [])\n  return (\n    <>\n      <div>state: {JSON.stringify(state)}</div>\n    </>\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: () => {\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 = () => {\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (params, { state: s }) => {\n      // return (state: typeof s) => { // TypeScript < 3.9\n      return state => {\n        state.counter += params || 1\n      }\n    },\n    decrease: params => s => {\n      s.counter += params || 1\n    }\n  }\n}\n\nexport default Model(model)\n```\n\nJavaScript Example\n\n```js\nconst Model = {\n  actions: {\n    increment: async (params) => {\n      return state => {\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<details>\n<summary>Store: shared.ts</summary>\n<p>\n\n```ts\nconst initialState = {\n  counter: 0\n}\n\nconst model: ModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: (params, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    }\n  },\n  // Provide for SSR\n  asyncState: async context => {\n    await waitFor(4000)\n    return { counter: 500 }\n  },\n  state: initialState\n}\n\nexport default Model(model)\n```\n\n</p>\n</details>\n\n<details>\n<summary>Global Config: _app.tsx</summary>\n<p>\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) => {\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    <Container>\n      <Component {...pageProps} />\n    </Container>\n  )\n}\n\nMyApp.getInitialProps = async (context: NextAppContext) => {\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</p>\n</details>\n\n<details>\n<summary>Page: hooks/index.tsx</summary>\n<p>\n\n```tsx\nimport { useStore, getState } from '../index'\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button\n        onClick={e => {\n          actions.increment(33)\n        }}\n      >\n    </div>\n  )\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Single Page Config: benchmark.tsx</summary>\n<p>\n\n```tsx\n// ...\nBenchmark.getInitialProps = async () => {\n  return await getInitialState({ modelName: 'Todo' }, { isServer: true })\n}\n```\n</p>\n</details>\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<{}> = async (context, restMiddlewares) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: any) => 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) => async (params: any) => {\n  const context: Context = {\n    modelName,\n    setState,\n    actionName: action.name,\n    next: () => {},\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<\n  { name: string }, // State Type\n  { ext: undefined }, // ActionParamsType\n  { name: string } // ExtContextType\n> = {\n  actions: {\n    // { state, action } => { state, action, [name] }\n    ext: (_, { name }) => {\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 => '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      <Provider>\n        <Counter />\n      </Provider>\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<details>\n<summary>Javascript decorator version</summary>\n<p>\n\n```jsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\n\nconst mapProps = ({ light, counter }) => ({\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      <>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(5)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n      </>\n    )\n  }\n}\n```\n\n</p>\n</details>\n\n<details>\n<summary>TypeScript Version</summary>\n<p>\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) => ({\n  lightStatus: light ? 'open' : 'close',\n  counter,\n  response\n})\n\ntype RType = ReturnType<typeof mapProps>\n\nclass TSCounter extends PureComponent<\n  { state: RType } & { actions: ActionType }\n> {\n  render() {\n    const { state, actions } = this.props\n    return (\n      <>\n        <div>TS Counter</div>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(3)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n        <button onClick={e => actions.get()}>Get Response</button>\n        <div>message: {JSON.stringify(state.response)}</div>\n      </>\n    )\n  }\n}\n\nexport default connect(\n  'Home',\n  mapProps\n)(TSCounter)\n```\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\n\n## FAQ\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 }) => ['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) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: Error) => 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) => {\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      <div className=\"App\">\n        <Counter />\n        <Counter />\n        <Counter />\n      </div>\n    )\n  }\n}\n```\n\n<details>\n<summary>Counter model</summary>\n<p>\n\n```ts\ninterface State {\n  count: number\n}\n\ninterface ActionParams {\n  increment: number\n}\n\nconst model: ModelType<State, ActionParams> = {\n  state: {\n    count: 0\n  },\n  actions: {\n    increment: payload => {\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 => (state.count += payload)\n      return state => {\n        state.count += payload\n      }\n    }\n  }\n}\n\n```\n\n</p>\n</details>\n\n<details>\n<summary>Counter.tsx</summary>\n<p>\n\n```tsx\n\nconst Counter = () => {\n  const [{ useStore }] = useState(() => Model(model))\n  const [state, actions] = useStore()\n  return (\n    <div>\n      <div>{state.count}</div>\n      <button onClick={() => actions.increment(3)}>Increment</button>\n    </div>\n  )\n}\n\nexport default Counter\n```\n\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\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 => {\n    // Not allowed\n    // return state => (state.count += payload)\n    return state => {\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) => {\n  await timeout(1000, {})\n  context.next(restMiddlewares)\n}\n\nconst nextCounterModel: ModelType<CounterState, NextCounterActionParams> = {\n  actions: {\n    add: num => {\n      return state => {\n        state.count += num\n      }\n    },\n    increment: async (num, { actions }) => {\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","readmeFilename":"README.md","gitHead":"403a7e2d8a50e846267a6036bc59fc8339e4ab21","_id":"react-model@4.0.0-rc.5","_nodeVersion":"14.0.0","_npmVersion":"6.14.4","dist":{"integrity":"sha512-GBV1pjiHPXp2QelfPa5OeSNYD96FAlp6OUoDLu6Np0a456RBoP25Wicf1Ci6xlGayOPebi4F6DQSQJ9liKHmVg==","shasum":"6cf1ac2b9334cee0846e3fcee2208d50e2170c83","tarball":"https://registry.npmjs.org/react-model/-/react-model-4.0.0-rc.5.tgz","fileCount":22,"unpackedSize":95310,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfcxVuCRA9TVsSAnZWagAALw8QAJqRLRSR/KhVzP8Os93A\n81VIJ/ih+jwRWpp+BuJqom5kPxk6SYMyyOPtqikilgsoYVSuzYlkhl9gbhF4\nSC/CuTMarjDyGSPpXLwjWvIOVnEmDvDcEiup5b5gcydunOJiXZAIyuI0azg3\neI5QsbTUjoCUmheyN7noYkzBcsjvLxHqrTf7s1Qj9i1zb3yNGLZk5Yl2IJ2S\nbDh9izpKA+NW2rz4CFBLdn9h9hyCud9TfHEEnK53qjwHS3kIJulAtLIoun5y\n5mm+WuH01W4jG2nk9vdos1QanJs7Lr6wli3LAt9xoCZy/eppgGksNXF9pM8a\nknCmjoTtiwnfaDPvkzHioRhZv30sMee30YCJ6Iov7Qzr+A0T/gir85HR6tYk\nWHsf8Uqr6SSZ+7d78u8biLqoZYlfsG0yHG1FW+xahBBr0VXKFM0Ahuyje6Og\n2Yw6cg0KSPYznU6frSn1SRId4Xph46c5E6gwPBclQiQ4i5PXnfZ+Pw7CEcut\nxrt6dmPdo0rw1ZncxTYcNYqFgSbzl2Dyuk+kMNYPtLG9u6FB1EYphY9ICQ00\npy27E9sEmbLq2xaXcd67w/ylKjVbvEdj4ncxsw3Fh04sbHJpmWnSpbyEv/sw\nFh4+/SiBDP0agBArpFt8nBHhLYfnGlosLe6/WGRMV5yUFiFo4j0qC9ZEcoMU\nS8+L\r\n=VYuO\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCyl9K2PdSTrcKOdKs7X4WbVYfFAQ33iZa0ejZSq7lvAwIgQKGZSK4oDWVW36MRwG4xfKeNC+3Y2q9yOr7GDIElQis="}]},"maintainers":[{"name":"leecade","email":"leecade@163.com"},{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"}],"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_4.0.0-rc.5_1601377645935_0.765637673126212"},"_hasShrinkwrap":false},"4.0.0-rc.6":{"name":"react-model","version":"4.0.0-rc.6","description":"The State management library for React","main":"./dist/react-model.js","module":"./dist/react-model.esm.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build":"microbundle --sourcemap false --jsx React.createElement --output dist --tsconfig ./tsconfig.json","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":"^6.0.0"},"peerDependencies":{"react":">=16.3.0","react-dom":">=16.3.0"},"devDependencies":{"@commitlint/cli":"^8.1.0","@commitlint/config-conventional":"^8.1.0","@testing-library/react":"^10.0.1","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/jest":"^25.1.0","@types/node":"^14.0.0","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","husky":"^4.0.2","jest":"^24.1.0","microbundle":"^0.12.3","prettier":"^2.0.0","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^8.0.0","remark-lint":"^7.0.0","remark-preset-lint-recommended":"^4.0.0","ts-jest":"^26.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"browserslist":["edge 17","firefox 70","chrome 48","safari 12.1","android 4.0","samsung 9.2"],"readme":"# react-model &middot; ![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) [![Build Status](https://travis-ci.org/byte-fe/react-model.svg?branch=master)](https://travis-ci.org/byte-fe/react-model) [![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 { Model } from 'react-model'\n\n// define model\nconst Todo = {\n  state: {\n    items: ['Install react-model', 'Read github docs', 'Build App']\n  },\n  actions: {\n    add: todo => {\n      // s is the readonly version of state\n      // you can also return partial state here but don't need to keep immutable manually\n      // state is the mutable state\n      return state => {\n        state.items.push(todo)\n      }\n    }\n  }\n}\n\n// Model Register\nconst { useStore } = Model(Todo)\n\nconst App = () => {\n  return <TodoList />\n}\n\nconst TodoList = () => {\n  const [state, actions] = useStore()\n  return <div>\n    <Addon handler={actions.add} />\n    {state.items.map((item, index) => (<Todo key={index} item={item} />))}\n  </div>\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[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  - [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  - [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  - [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### 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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (payload, { state }) => {\n      return {\n        counter: state.counter + (payload || 1)\n      }\n    },\n    openLight: async (_, { state, actions }) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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(model)\n\n// You can use these types when use Class Components.\n// type ConsumerActionsType = getConsumerActionsType<typeof Model.actions>\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 () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button onClick={e => actions.increment(33)}>home increment</button>\n      <button onClick={e => sharedActions.increment(20)}>\n        shared increment\n      </button>\n      <button onClick={e => actions.get()}>fake request</button>\n      <button onClick={e => actions.openLight()}>fake nested call</button>\n    </div>\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 >= 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> Hint: The state returned should only be used as readonly\n\n```jsx\nimport { useStore, getState } from '../model/index'\n\nconst BasicHook = () => {\n  const [state, actions] = useStore('Counter')\n  useEffect(() => {\n    console.log('some mounted actions from BasicHooks')\n    return () =>\n      console.log(\n        `Basic Hooks unmounted, current Counter state: ${JSON.stringify(\n          getState('Counter')\n        )}`\n      )\n  }, [])\n  return (\n    <>\n      <div>state: {JSON.stringify(state)}</div>\n    </>\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: () => {\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 = () => {\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (params, { state: s }) => {\n      // return (state: typeof s) => { // TypeScript < 3.9\n      return state => {\n        state.counter += params || 1\n      }\n    },\n    decrease: params => s => {\n      s.counter += params || 1\n    }\n  }\n}\n\nexport default Model(model)\n```\n\nJavaScript Example\n\n```js\nconst Model = {\n  actions: {\n    increment: async (params) => {\n      return state => {\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<details>\n<summary>Store: shared.ts</summary>\n<p>\n\n```ts\nconst initialState = {\n  counter: 0\n}\n\nconst model: ModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: (params, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    }\n  },\n  // Provide for SSR\n  asyncState: async context => {\n    await waitFor(4000)\n    return { counter: 500 }\n  },\n  state: initialState\n}\n\nexport default Model(model)\n```\n\n</p>\n</details>\n\n<details>\n<summary>Global Config: _app.tsx</summary>\n<p>\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) => {\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    <Container>\n      <Component {...pageProps} />\n    </Container>\n  )\n}\n\nMyApp.getInitialProps = async (context: NextAppContext) => {\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</p>\n</details>\n\n<details>\n<summary>Page: hooks/index.tsx</summary>\n<p>\n\n```tsx\nimport { useStore, getState } from '../index'\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button\n        onClick={e => {\n          actions.increment(33)\n        }}\n      >\n    </div>\n  )\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Single Page Config: benchmark.tsx</summary>\n<p>\n\n```tsx\n// ...\nBenchmark.getInitialProps = async () => {\n  return await getInitialState({ modelName: 'Todo' }, { isServer: true })\n}\n```\n</p>\n</details>\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<{}> = async (context, restMiddlewares) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: any) => 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) => async (params: any) => {\n  const context: Context = {\n    modelName,\n    setState,\n    actionName: action.name,\n    next: () => {},\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<\n  { name: string }, // State Type\n  { ext: undefined }, // ActionParamsType\n  { name: string } // ExtContextType\n> = {\n  actions: {\n    // { state, action } => { state, action, [name] }\n    ext: (_, { name }) => {\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 => '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      <Provider>\n        <Counter />\n      </Provider>\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<details>\n<summary>Javascript decorator version</summary>\n<p>\n\n```jsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\n\nconst mapProps = ({ light, counter }) => ({\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      <>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(5)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n      </>\n    )\n  }\n}\n```\n\n</p>\n</details>\n\n<details>\n<summary>TypeScript Version</summary>\n<p>\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) => ({\n  lightStatus: light ? 'open' : 'close',\n  counter,\n  response\n})\n\ntype RType = ReturnType<typeof mapProps>\n\nclass TSCounter extends PureComponent<\n  { state: RType } & { actions: ActionType }\n> {\n  render() {\n    const { state, actions } = this.props\n    return (\n      <>\n        <div>TS Counter</div>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(3)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n        <button onClick={e => actions.get()}>Get Response</button>\n        <div>message: {JSON.stringify(state.response)}</div>\n      </>\n    )\n  }\n}\n\nexport default connect(\n  'Home',\n  mapProps\n)(TSCounter)\n```\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\n\n## FAQ\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 }) => ['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) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: Error) => 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) => {\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      <div className=\"App\">\n        <Counter />\n        <Counter />\n        <Counter />\n      </div>\n    )\n  }\n}\n```\n\n<details>\n<summary>Counter model</summary>\n<p>\n\n```ts\ninterface State {\n  count: number\n}\n\ninterface ActionParams {\n  increment: number\n}\n\nconst model: ModelType<State, ActionParams> = {\n  state: {\n    count: 0\n  },\n  actions: {\n    increment: payload => {\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 => (state.count += payload)\n      return state => {\n        state.count += payload\n      }\n    }\n  }\n}\n\n```\n\n</p>\n</details>\n\n<details>\n<summary>Counter.tsx</summary>\n<p>\n\n```tsx\n\nconst Counter = () => {\n  const [{ useStore }] = useState(() => Model(model))\n  const [state, actions] = useStore()\n  return (\n    <div>\n      <div>{state.count}</div>\n      <button onClick={() => actions.increment(3)}>Increment</button>\n    </div>\n  )\n}\n\nexport default Counter\n```\n\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\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 => {\n    // Not allowed\n    // return state => (state.count += payload)\n    return state => {\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) => {\n  await timeout(1000, {})\n  context.next(restMiddlewares)\n}\n\nconst nextCounterModel: ModelType<CounterState, NextCounterActionParams> = {\n  actions: {\n    add: num => {\n      return state => {\n        state.count += num\n      }\n    },\n    increment: async (num, { actions }) => {\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","readmeFilename":"README.md","gitHead":"403a7e2d8a50e846267a6036bc59fc8339e4ab21","_id":"react-model@4.0.0-rc.6","_nodeVersion":"14.0.0","_npmVersion":"6.14.4","dist":{"integrity":"sha512-1s3zFU7xlzBoioKDU7AFQBZiAnz2vLzSyALcwiTc1sxESs3GQQu+/RF3kz3gQUkMdW2N7i+uEfJqk1tBccEFIw==","shasum":"36cff3d8d61bffc1b6591c215a677038ffbe06b8","tarball":"https://registry.npmjs.org/react-model/-/react-model-4.0.0-rc.6.tgz","fileCount":22,"unpackedSize":95423,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfczInCRA9TVsSAnZWagAAL1YP/0pA4DaAbbHlJum2aRnN\nKZOCX7//92Z2X2Y/qEa7/Zx3FDG+TxmmPWN5zzwwjfAzPUeKn0f+MZZbIjJ6\nzZQkM2NgTkcf0vL2iCJzT73ULLtttvtDk91gSa18zJor55d83vecsFUuixQz\n5cIrOARvZaDYMMimVamrY+GdXft//f0ifYa1am8MSr0FFY0w50/Fihr8G7Ec\nKFRhzBJllFiO+pPGncz/4wi3DlrB11iiZeqAkDBpw6vOy/SvI1K2THv4WqWj\nf3RS2/TdF6PvQl4HawgRtHitLO0UQRVvAcHPiUZzYwVlotCpDKXgTG4xHDUW\nGX/h7utvPfIlXy5wnUdgI3cwh/gZrN4i4itsdFo99o34ImTf/rSW+ReiQ5Za\ncTmzcNGEliwkUOTH5RcqH9piUZXv+7D0sUiGTWmYojWEQjQTYculXA0PYiJq\nlv/yBETcEaDnIO2fePwNGq/jiMT+s+j/zwt+uJPXUN08f0hjJvkBlSqSA+kE\nF+jRUmsor+PQ8nOjo3Bc1gNgowPw96ICFEP9PX7tOuti5tOm8Em4XtP0PLzd\nii24WC+/0EIyppV/UjAtTRKZgUTvZkCKrwSjiIMCXntY3JIlJvFHcugUkjWG\nUwEEJ61Z2JGbsNaMrCEqyHV4p/f8M9IDEZjmBIAIl9uR8uydcSNxq6iRwxNb\nUtV4\r\n=eFQW\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCjoSktC60FWfDwj1tdz5wUJxPNhGPgWP512zUg8RZ+zgIgAdCj66n3fvxfNcPpLxgsddHnfFCT95AmB3+uw7cbWX8="}]},"maintainers":[{"name":"leecade","email":"leecade@163.com"},{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"}],"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_4.0.0-rc.6_1601384999448_0.5349159543834878"},"_hasShrinkwrap":false},"4.0.0":{"name":"react-model","version":"4.0.0","description":"The State management library for React","main":"./dist/react-model.js","module":"./dist/react-model.esm.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build":"microbundle --sourcemap false --jsx React.createElement --output dist --tsconfig ./tsconfig.json","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":">=8.0.1"},"peerDependencies":{"react":">=16.3.0","react-dom":">=16.3.0"},"devDependencies":{"@commitlint/cli":"^8.1.0","@commitlint/config-conventional":"^8.1.0","@testing-library/react":"^10.0.1","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/jest":"^25.1.0","@types/node":"^14.0.0","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","husky":"^4.0.2","jest":"^24.1.0","microbundle":"^0.12.3","prettier":"^2.0.0","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^8.0.0","remark-lint":"^7.0.0","remark-preset-lint-recommended":"^4.0.0","ts-jest":"^26.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"browserslist":["edge 17","firefox 70","chrome 48","safari 12.1","android 4.0","samsung 9.2"],"gitHead":"0346ea0605d004d89932e17362df9936140ddd5c","_id":"react-model@4.0.0","_nodeVersion":"14.8.0","_npmVersion":"6.14.7","dist":{"integrity":"sha512-SJSq8g3ndjfZl4DYdehMaH5fqUkEambVhK+c91adhhtsaswWir3iiqPVCwxwriucIe61ps/7zeaLfJe3RuzCvQ==","shasum":"caddcb5f8856e8261f17c38dbf44c6a5b9c55375","tarball":"https://registry.npmjs.org/react-model/-/react-model-4.0.0.tgz","fileCount":22,"unpackedSize":95420,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgQbz1CRA9TVsSAnZWagAA+s8P/jz0MEeFuDkqW8vJOfuv\nPuDND4bFe1MCVHDsTzFtNQgXUcQzujRMgUrKbZJWDUbQzsSAu//t6BUJKklA\nt2WcJHVVaTrI4uBE6RgL1HpLdPyqCjxF71zkHjt+6D5CR37eWfIH5IqiRztP\nCGFSG9umFEZeX0vKlfZOsDD5XxmMr39k9dXYhgqXGGufgysVwi3GafichOEP\nwuGnjiSbk8mL6I2ElXB5B6KfkRkNEnOmFjEIojbV+cH8aL6zrN14CjS2feF/\nPgJqZpTZkr51RUAc8EsISqcl9Ctpqswlyix4wK2OJOQoiwua5PjLAupdJayL\nAPBwwUZFpHeQEaim4ujgrPR/7Z9pjOxclJf1UPiCt5VtK08w9FY3Tiji/3c7\nsI8NtUVfgaQq2g11B/sGUX1efNJbzp/MGhy2El2+VYy5fHIFG1mLzD2nYE+A\nvrljT8ohN80qV3qC2f6dpKR2vjpEhen3l4QC1M33Q45Pl2Gy6rT4qTwhpCRO\nag4mUORzp0GPZU8h/EpDnZrZVzbtiNZpz6qMFdkU6eyHIq/VPdbqjWAZFjNN\nI3uYoPjbsga6MA0NuJemF7OQMZQdoiEMPPfaQHE2N6qTdwecXtpmOMI2O25Q\njbTKDHA+wgdvwnwW6O02d4CLFb7RMAPBf8+UXmtdflWqILweUJLmXULllVnR\ndhhE\r\n=Ypoq\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDp0rth6Znd5MA2jGOBDkIuf3WL80z7e3OVE4RwHtZfhAIhANDQ8AyEViEKNAs7+5/P9rHlHhlvKmAdcS5HIJuODnw1"}]},"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"maintainers":[{"name":"leecade","email":"leecade@163.com"},{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_4.0.0_1614920948874_0.29364437174198343"},"_hasShrinkwrap":false},"4.0.1-rc.0":{"name":"react-model","version":"4.0.1-rc.0","description":"The State management library for React","main":"./dist/react-model.js","module":"./dist/react-model.esm.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build":"microbundle --sourcemap false --jsx React.createElement --output dist --tsconfig ./tsconfig.json","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":">=8.0.1"},"peerDependencies":{"react":">=16.3.0","react-dom":">=16.3.0"},"devDependencies":{"@commitlint/cli":"^8.1.0","@commitlint/config-conventional":"^8.1.0","@testing-library/react":"^10.0.1","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/jest":"^25.1.0","@types/node":"^14.0.0","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","husky":"^4.0.2","jest":"^24.1.0","microbundle":"^0.12.3","prettier":"^2.0.0","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^8.0.0","remark-lint":"^7.0.0","remark-preset-lint-recommended":"^4.0.0","ts-jest":"^26.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"browserslist":["edge 17","firefox 70","chrome 48","safari 12.1","android 4.0","samsung 9.2"],"readme":"# react-model &middot; ![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) [![Build Status](https://travis-ci.org/byte-fe/react-model.svg?branch=master)](https://travis-ci.org/byte-fe/react-model) [![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 { Model } from 'react-model'\n\n// define model\nconst Todo = {\n  state: {\n    items: ['Install react-model', 'Read github docs', 'Build App']\n  },\n  actions: {\n    add: todo => {\n      // s is the readonly version of state\n      // you can also return partial state here but don't need to keep immutable manually\n      // state is the mutable state\n      return state => {\n        state.items.push(todo)\n      }\n    }\n  }\n}\n\n// Model Register\nconst { useStore } = Model(Todo)\n\nconst App = () => {\n  return <TodoList />\n}\n\nconst TodoList = () => {\n  const [state, actions] = useStore()\n  return <div>\n    <Addon handler={actions.add} />\n    {state.items.map((item, index) => (<Todo key={index} item={item} />))}\n  </div>\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[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  - [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 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  - [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### 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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (payload, { state }) => {\n      return {\n        counter: state.counter + (payload || 1)\n      }\n    },\n    openLight: async (_, { state, actions }) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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<typeof Model.actions>\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 () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button onClick={e => actions.increment(33)}>home increment</button>\n      <button onClick={e => sharedActions.increment(20)}>\n        shared increment\n      </button>\n      <button onClick={e => actions.get()}>fake request</button>\n      <button onClick={e => actions.openLight()}>fake nested call</button>\n    </div>\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 >= 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> Hint: The state returned should only be used as readonly\n\n```jsx\nimport { useStore, getState } from '../model/index'\n\nconst BasicHook = () => {\n  const [state, actions] = useStore('Counter')\n  useEffect(() => {\n    console.log('some mounted actions from BasicHooks')\n    return () =>\n      console.log(\n        `Basic Hooks unmounted, current Counter state: ${JSON.stringify(\n          getState('Counter')\n        )}`\n      )\n  }, [])\n  return (\n    <>\n      <div>state: {JSON.stringify(state)}</div>\n    </>\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: () => {\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 = () => {\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (params, { state: s }) => {\n      // return (state: typeof s) => { // TypeScript < 3.9\n      return state => {\n        state.counter += params || 1\n      }\n    },\n    decrease: params => s => {\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) => {\n      return state => {\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<details>\n<summary>Store: shared.ts</summary>\n<p>\n\n```ts\nconst initialState = {\n  counter: 0\n}\n\nconst model: ModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: (params, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    }\n  },\n  // Provide for SSR\n  asyncState: async context => {\n    await waitFor(4000)\n    return { counter: 500 }\n  },\n  state: initialState\n}\n\nexport default model\n```\n\n</p>\n</details>\n\n<details>\n<summary>Global Config: _app.tsx</summary>\n<p>\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) => {\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    <Container>\n      <Component {...pageProps} />\n    </Container>\n  )\n}\n\nMyApp.getInitialProps = async (context: NextAppContext) => {\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</p>\n</details>\n\n<details>\n<summary>Page: hooks/index.tsx</summary>\n<p>\n\n```tsx\nimport { useStore, getState } from '../index'\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button\n        onClick={e => {\n          actions.increment(33)\n        }}\n      >\n    </div>\n  )\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Single Page Config: benchmark.tsx</summary>\n<p>\n\n```tsx\n// ...\nBenchmark.getInitialProps = async () => {\n  return await getInitialState({ modelName: 'Todo' }, { isServer: true })\n}\n```\n</p>\n</details>\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<{}> = async (context, restMiddlewares) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: any) => 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) => async (params: any) => {\n  const context: Context = {\n    modelName,\n    setState,\n    actionName: action.name,\n    next: () => {},\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<\n  { name: string }, // State Type\n  { ext: undefined }, // ActionParamsType\n  { name: string } // ExtContextType\n> = {\n  actions: {\n    // { state, action } => { state, action, [name] }\n    ext: (_, { name }) => {\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 => '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      <Provider>\n        <Counter />\n      </Provider>\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<details>\n<summary>Javascript decorator version</summary>\n<p>\n\n```jsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\n\nconst mapProps = ({ light, counter }) => ({\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      <>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(5)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n      </>\n    )\n  }\n}\n```\n\n</p>\n</details>\n\n<details>\n<summary>TypeScript Version</summary>\n<p>\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) => ({\n  lightStatus: light ? 'open' : 'close',\n  counter,\n  response\n})\n\ntype RType = ReturnType<typeof mapProps>\n\nclass TSCounter extends PureComponent<\n  { state: RType } & { actions: ActionType }\n> {\n  render() {\n    const { state, actions } = this.props\n    return (\n      <>\n        <div>TS Counter</div>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(3)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n        <button onClick={e => actions.get()}>Get Response</button>\n        <div>message: {JSON.stringify(state.response)}</div>\n      </>\n    )\n  }\n}\n\nexport default connect(\n  'Home',\n  mapProps\n)(TSCounter)\n```\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\n\n## FAQ\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<State, ActionParams> = {\n  state: {\n    counter: 1\n    enable: false\n  },\n  actions: {\n    add: (payload) => state => {\n      state.counter += payload\n    },\n    switch: () => state => {\n      state.enable = !state.enable\n    }\n  }\n}\n```\n\n```ts\nconst Component = () => {\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 => 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 }) => ['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) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: Error) => 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) => {\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      <div className=\"App\">\n        <Counter />\n        <Counter />\n        <Counter />\n      </div>\n    )\n  }\n}\n```\n\n<details>\n<summary>Counter model</summary>\n<p>\n\n```ts\ninterface State {\n  count: number\n}\n\ninterface ActionParams {\n  increment: number\n}\n\nconst model: ModelType<State, ActionParams> = {\n  state: {\n    count: 0\n  },\n  actions: {\n    increment: payload => {\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 => (state.count += payload)\n      return state => {\n        state.count += payload\n      }\n    }\n  }\n}\n\n```\n\n</p>\n</details>\n\n<details>\n<summary>Counter.tsx</summary>\n<p>\n\n```tsx\n\nconst Counter = () => {\n  const [{ useStore }] = useState(() => Model(model))\n  const [state, actions] = useStore()\n  return (\n    <div>\n      <div>{state.count}</div>\n      <button onClick={() => actions.increment(3)}>Increment</button>\n    </div>\n  )\n}\n\nexport default Counter\n```\n\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\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 => {\n    // Not allowed\n    // return state => (state.count += payload)\n    return state => {\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) => {\n  await timeout(1000, {})\n  context.next(restMiddlewares)\n}\n\nconst nextCounterModel: ModelType<CounterState, NextCounterActionParams> = {\n  actions: {\n    add: num => {\n      return state => {\n        state.count += num\n      }\n    },\n    increment: async (num, { actions }) => {\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","readmeFilename":"README.md","gitHead":"2372378a2f44cb71fd46f536c2b59a0f1b9aa7ee","_id":"react-model@4.0.1-rc.0","_nodeVersion":"14.8.0","_npmVersion":"6.14.7","dist":{"integrity":"sha512-Iit4w3ypsN6Z8qD5k5xz3LI+w4dJGAy+1tBDURlSj9oBsLWVKpm8mZEyUbvSLrK5ZeaZ+b1DmhY9hfn4f+YNyg==","shasum":"a7245a0cf538e17d3ce5be70c7d98b1123ae113f","tarball":"https://registry.npmjs.org/react-model/-/react-model-4.0.1-rc.0.tgz","fileCount":22,"unpackedSize":96435,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgd+ueCRA9TVsSAnZWagAAz9EP/AubQ6kD4/1MNxCg8cdK\n9irbLqdOthIQK04kU3Bcm8y9JTJWnpU7vymeJwlsHfSEhaD7Wj4a6Mdbwn9L\ndzCDPTTyC1L/RIQzp0u9UR1zGBjPBBm0aAxS4vvrvxrKr7Ty0fafYPWfSK5P\neIJtlpe20XXNxraL2bDmWUv/mPDLInlDsJavbPB7avbQxoXN3nwxY+aNUKMa\nExydKqxGfVbHl+SCvLZm0JZDpFd2QlTkdcsANe2qOZPbnCFDruPJQ4x7+M2N\nhAVyb/gzMgfP8g5G9NXOgRHb7n+nykpibRyNb0CX1Wj6E4uQejLnDZg0f7u8\n3NxzKedDhqtOXTmRRXjuPcVNKWW2/9tAt8LCeX+DDmrfyi3y3c8XeN5k41bX\nWN4TkVeNivsx+H7KxNige/5KmAAJAs5nrAvSjpS4Z1v/EE5R1zUEwR5ok0v3\nAxKwJSpxkciP9jaMr637GxOaC6QID5RYMXCGtkJ3GVh/ufWdaLeoPm9mw7Kk\nW6RdudRYTW6HtZrfcB4Nzhhl+W9FVxABuFa+jEn26XeEjn+If9wnWV5huXKf\nK84AoNTt7DgMtZIBei4VFQPkMW6O0AAhPocOUYaFKkAD+Zjhx4MIAjyJVtam\nXage60/wCR7+L4h620DL5kKidGteWRQNuqg9vu1nEaxLtCNdtVKlqiWpDiXu\n1esC\r\n=URig\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIAmhOwL9hphomqcdx4gblE09AVjbQ79aZZR/eBo/PJp/AiAUzGhhxGAWbgi35IbfNSWwXvygvDQY5Qwg22Wpvq8amA=="}]},"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"maintainers":[{"name":"leecade","email":"leecade@163.com"},{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_4.0.1-rc.0_1618471837850_0.9615123789609548"},"_hasShrinkwrap":false},"4.0.1-rc.1":{"name":"react-model","version":"4.0.1-rc.1","description":"The State management library for React","main":"./dist/react-model.js","module":"./dist/react-model.esm.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build":"microbundle --sourcemap false --jsx React.createElement --output dist --tsconfig ./tsconfig.json","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":">=8.0.1"},"peerDependencies":{"react":">=16.3.0","react-dom":">=16.3.0","typescript":">=3.9.0"},"devDependencies":{"@commitlint/cli":"^8.1.0","@commitlint/config-conventional":"^8.1.0","@testing-library/react":"^10.0.1","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/jest":"^25.1.0","@types/node":"^14.0.0","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","husky":"^4.0.2","jest":"^24.1.0","microbundle":"^0.12.3","prettier":"^2.0.0","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^8.0.0","remark-lint":"^7.0.0","remark-preset-lint-recommended":"^4.0.0","ts-jest":"^26.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"browserslist":["edge 17","firefox 70","chrome 48","safari 12.1","android 4.0","samsung 9.2"],"readme":"# react-model &middot; ![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) [![Build Status](https://travis-ci.org/byte-fe/react-model.svg?branch=master)](https://travis-ci.org/byte-fe/react-model) [![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 { Model } from 'react-model'\n\n// define model\nconst Todo = {\n  state: {\n    items: ['Install react-model', 'Read github docs', 'Build App']\n  },\n  actions: {\n    add: todo => {\n      // s is the readonly version of state\n      // you can also return partial state here but don't need to keep immutable manually\n      // state is the mutable state\n      return state => {\n        state.items.push(todo)\n      }\n    }\n  }\n}\n\n// Model Register\nconst { useStore } = Model(Todo)\n\nconst App = () => {\n  return <TodoList />\n}\n\nconst TodoList = () => {\n  const [state, actions] = useStore()\n  return <div>\n    <Addon handler={actions.add} />\n    {state.items.map((item, index) => (<Todo key={index} item={item} />))}\n  </div>\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[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  - [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 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  - [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### 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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (payload, { state }) => {\n      return {\n        counter: state.counter + (payload || 1)\n      }\n    },\n    openLight: async (_, { state, actions }) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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<typeof Model.actions>\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 () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button onClick={e => actions.increment(33)}>home increment</button>\n      <button onClick={e => sharedActions.increment(20)}>\n        shared increment\n      </button>\n      <button onClick={e => actions.get()}>fake request</button>\n      <button onClick={e => actions.openLight()}>fake nested call</button>\n    </div>\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 >= 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> Hint: The state returned should only be used as readonly\n\n```jsx\nimport { useStore, getState } from '../model/index'\n\nconst BasicHook = () => {\n  const [state, actions] = useStore('Counter')\n  useEffect(() => {\n    console.log('some mounted actions from BasicHooks')\n    return () =>\n      console.log(\n        `Basic Hooks unmounted, current Counter state: ${JSON.stringify(\n          getState('Counter')\n        )}`\n      )\n  }, [])\n  return (\n    <>\n      <div>state: {JSON.stringify(state)}</div>\n    </>\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: () => {\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 = () => {\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (params, { state: s }) => {\n      // return (state: typeof s) => { // TypeScript < 3.9\n      return state => {\n        state.counter += params || 1\n      }\n    },\n    decrease: params => s => {\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) => {\n      return state => {\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<details>\n<summary>Store: shared.ts</summary>\n<p>\n\n```ts\nconst initialState = {\n  counter: 0\n}\n\nconst model: ModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: (params, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    }\n  },\n  // Provide for SSR\n  asyncState: async context => {\n    await waitFor(4000)\n    return { counter: 500 }\n  },\n  state: initialState\n}\n\nexport default model\n```\n\n</p>\n</details>\n\n<details>\n<summary>Global Config: _app.tsx</summary>\n<p>\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) => {\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    <Container>\n      <Component {...pageProps} />\n    </Container>\n  )\n}\n\nMyApp.getInitialProps = async (context: NextAppContext) => {\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</p>\n</details>\n\n<details>\n<summary>Page: hooks/index.tsx</summary>\n<p>\n\n```tsx\nimport { useStore, getState } from '../index'\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button\n        onClick={e => {\n          actions.increment(33)\n        }}\n      >\n    </div>\n  )\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Single Page Config: benchmark.tsx</summary>\n<p>\n\n```tsx\n// ...\nBenchmark.getInitialProps = async () => {\n  return await getInitialState({ modelName: 'Todo' }, { isServer: true })\n}\n```\n</p>\n</details>\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<{}> = async (context, restMiddlewares) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: any) => 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) => async (params: any) => {\n  const context: Context = {\n    modelName,\n    setState,\n    actionName: action.name,\n    next: () => {},\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<\n  { name: string }, // State Type\n  { ext: undefined }, // ActionParamsType\n  { name: string } // ExtContextType\n> = {\n  actions: {\n    // { state, action } => { state, action, [name] }\n    ext: (_, { name }) => {\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 => '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      <Provider>\n        <Counter />\n      </Provider>\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<details>\n<summary>Javascript decorator version</summary>\n<p>\n\n```jsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\n\nconst mapProps = ({ light, counter }) => ({\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      <>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(5)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n      </>\n    )\n  }\n}\n```\n\n</p>\n</details>\n\n<details>\n<summary>TypeScript Version</summary>\n<p>\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) => ({\n  lightStatus: light ? 'open' : 'close',\n  counter,\n  response\n})\n\ntype RType = ReturnType<typeof mapProps>\n\nclass TSCounter extends PureComponent<\n  { state: RType } & { actions: ActionType }\n> {\n  render() {\n    const { state, actions } = this.props\n    return (\n      <>\n        <div>TS Counter</div>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(3)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n        <button onClick={e => actions.get()}>Get Response</button>\n        <div>message: {JSON.stringify(state.response)}</div>\n      </>\n    )\n  }\n}\n\nexport default connect(\n  'Home',\n  mapProps\n)(TSCounter)\n```\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\n\n## FAQ\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<State, ActionParams> = {\n  state: {\n    counter: 1\n    enable: false\n  },\n  actions: {\n    add: (payload) => state => {\n      state.counter += payload\n    },\n    switch: () => state => {\n      state.enable = !state.enable\n    }\n  }\n}\n```\n\n```ts\nconst Component = () => {\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 => 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 }) => ['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) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: Error) => 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) => {\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      <div className=\"App\">\n        <Counter />\n        <Counter />\n        <Counter />\n      </div>\n    )\n  }\n}\n```\n\n<details>\n<summary>Counter model</summary>\n<p>\n\n```ts\ninterface State {\n  count: number\n}\n\ninterface ActionParams {\n  increment: number\n}\n\nconst model: ModelType<State, ActionParams> = {\n  state: {\n    count: 0\n  },\n  actions: {\n    increment: payload => {\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 => (state.count += payload)\n      return state => {\n        state.count += payload\n      }\n    }\n  }\n}\n\n```\n\n</p>\n</details>\n\n<details>\n<summary>Counter.tsx</summary>\n<p>\n\n```tsx\n\nconst Counter = () => {\n  const [{ useStore }] = useState(() => Model(model))\n  const [state, actions] = useStore()\n  return (\n    <div>\n      <div>{state.count}</div>\n      <button onClick={() => actions.increment(3)}>Increment</button>\n    </div>\n  )\n}\n\nexport default Counter\n```\n\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\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 => {\n    // Not allowed\n    // return state => (state.count += payload)\n    return state => {\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) => {\n  await timeout(1000, {})\n  context.next(restMiddlewares)\n}\n\nconst nextCounterModel: ModelType<CounterState, NextCounterActionParams> = {\n  actions: {\n    add: num => {\n      return state => {\n        state.count += num\n      }\n    },\n    increment: async (num, { actions }) => {\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","readmeFilename":"README.md","gitHead":"e6d8b0ea55eacce35259f13143972525c6cd7d13","_id":"react-model@4.0.1-rc.1","_nodeVersion":"12.10.0","_npmVersion":"6.10.3","dist":{"integrity":"sha512-V8CHjv3zML0YjfPIPBBo4u9zoN2GTKCQeBJRu8e/BKEzxfoad6s0J8TuagxuFCTnQLL+KUPGv01J8VFgcUn+8g==","shasum":"c5e1aed3b7a08378b945fd1e785d12d0bf7b657f","tarball":"https://registry.npmjs.org/react-model/-/react-model-4.0.1-rc.1.tgz","fileCount":22,"unpackedSize":96359,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJggUwPCRA9TVsSAnZWagAAu+wP/jRbggq0UsJHIgz8T2gW\n3kSdQKY0JAWEsIb+8dNxYt2rTaaUfHpTkGXkP9jxQEhF0+yuVf2L4bHxzBXa\nY6Ha1XpSLCCClAuB9e7BEfNEXdmShM8RgtNdtKcwEbGp61gQyaRU4eYCsPxq\n3oO/PR7fCHgXRe3OfuD3gHEbTWkWDY8kDSy/BEnO5pPxTS3v30JfUu1WTqBc\nZeO9vCR+a6s9ZUtCNC3R/6XyOcWebCSYGyePA7y/sr1qX5zeYYSVospJ30Tm\nQFULIlDYOBxfr4pRHa/NxKSs1aRqtQ/5kFZnEzFlwv6YhWMNbxvKnNn9CzuZ\nqcmnuX9T0Y/kCgukmdwA///ni55eM61NYvcy2txfF2Rs8G1+bF908Pb0Pg3P\n95jgNsoHtsqYDpEa+5q5G621O/QwLr7PXTa+Wvo/ESfNQ5L1lntxn0T2yOSw\nHYEJXd9xRoUo+0mKOScuhJkILO6hSotwKPZSJjBp+/Qes+dEK4c4D8KIxOgM\nwelbZdGbDe4qsVou8Ry3M7StSC7VrMH4hTWb/ID+J9WkdhqEh35SKKfg1ELd\nKYJDhdATyxAD8d5hdILVUOv1QZMo9cb+x88OerUMucjFgDsEkdEQmnRlCaJO\nvZtr1Ae7Laf1VZsYYTzlSvy0U4ouF23xJrFo6WRMY6KaiS+fpaoGM5ZB0QgA\ncDGR\r\n=MJFD\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIDP1Q0OnXay9Fxhb6IUNsC5EcV3Iaml0bTT5OYWaX2TEAiACIieWeSsUEfVobSiJc3HyLttDMST+5HyoR+TewFpKzQ=="}]},"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"maintainers":[{"name":"leecade","email":"leecade@163.com"},{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_4.0.1-rc.1_1619086350842_0.214299134791051"},"_hasShrinkwrap":false},"4.0.1":{"name":"react-model","version":"4.0.1","description":"The State management library for React","main":"./dist/react-model.js","module":"./dist/react-model.esm.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build:prod":"microbundle --define process.env.NODE_ENV=production --sourcemap false --jsx React.createElement --output dist --tsconfig ./tsconfig.json","build:dev":"microbundle --define process.env.NODE_ENV=development --sourcemap true --jsx React.createElement --output dist --tsconfig ./tsconfig.json","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":">=8.0.1"},"peerDependencies":{"react":">=16.3.0","react-dom":">=16.3.0","typescript":">=3.9.0"},"devDependencies":{"@commitlint/cli":"^8.1.0","@commitlint/config-conventional":"^8.1.0","@testing-library/react":"^10.0.1","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/faker":"^5.5.3","@types/jest":"^25.1.0","@types/node":"^14.0.0","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","faker":"^5.5.3","husky":"^4.0.2","jest":"^24.1.0","microbundle":"^0.12.3","prettier":"^2.0.0","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^8.0.0","remark-lint":"^7.0.0","remark-preset-lint-recommended":"^4.0.0","ts-jest":"^26.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"browserslist":["edge 17","firefox 70","chrome 48","safari 12.1","android 4.0","samsung 9.2"],"gitHead":"dd511ec44a912bd6be7a41f88f47f7bfef13026b","_id":"react-model@4.0.1","_nodeVersion":"12.10.0","_npmVersion":"7.10.0","dist":{"integrity":"sha512-Cftr68N9eOX6WqAGs676CUwj9iQjmRf/KkAcf1i4NntK/gZ8ebWAGW/iZS7aMnr5YAu6kecoOeju7gNgaULKqQ==","shasum":"670985b87e9453f02458edb087b2be681231aed2","tarball":"https://registry.npmjs.org/react-model/-/react-model-4.0.1.tgz","fileCount":22,"unpackedSize":97839,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgywCoCRA9TVsSAnZWagAAX08P/14Oa0FlMuaNVZmzarke\nyXClAEg6aXGllbrv1E+7LOlnkEJDGExRxWm/vXgkrhTJd2QeteQJUvOsjiTL\n9LZGdTzZdpc9l/mDcPxhgfUGRNDTe6Hyc/HNg+vuFuvXqZJxpjMEXN2jWErw\nfr9s6XmtcuTGLNIAC2r3g/eqHaTotTlauqsfk2Ify3562EOMVTIoI4tcNtnI\nVV8CnxAFC+AEGul15lZhseO4+BmQSBBeBprRbdiUWC+PREv9l1uf9YjiVNuU\nGWfrHoYJLsmdb8rvPZWGs7CjA4c8w3a5xX79YVLrcUHNNHevRljElY6yp/k6\nt2OnXdoGR0nH9ZyhOSQBuO1e1pCTpPHOaF6ywieXJDODaqUt2km+cB4xy7+L\n5JpYDmikXdZq549z8MFpUcxOoJcPuA0K5gqJrvfRhXk+SM3xNb54caxi4B4j\nCqRMk3L22YiZPQiCRyYcpB1TJQuWJ+CXFCUlK6PWd1pRgNL+7Xzp2VHsrrLz\nER8qOwn2poMEY9ZE0DeFNsGO4fBoHxeqztiESsWmqTvjmxXh2pgbGscpduCO\nZZVEvVl09/MOeCuQyLdXv7DaCaq+Tvdtiz8M3xSCoF1SQcKs8znxomglujzq\n6SSL62cAljxxSt3Stirxw4fIoGM9lZXceBSd3sU6sW+QDOy5ba7EATOx1T1V\nSPAw\r\n=jWq4\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDdP6U6tWoU9GVHg87f3yO0QsIfV/9Pl0mRivyNEmr9TQIgC6OT1VddQcDBRBI3DZrH3yO4ARc/UyQSaFcrko6I6Yo="}]},"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"maintainers":[{"name":"leecade","email":"leecade@163.com"},{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_4.0.1_1623916711032_0.1481832500557354"},"_hasShrinkwrap":false},"4.1.0-alpha.0":{"name":"react-model","version":"4.1.0-alpha.0","description":"The State management library for React","main":"./dist/react-model.js","module":"./dist/react-model.esm.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build:prod":"microbundle --define process.env.NODE_ENV=production --sourcemap false --jsx React.createElement --output dist --tsconfig ./tsconfig.json","build:dev":"microbundle --define process.env.NODE_ENV=development --sourcemap true --jsx React.createElement --output dist --tsconfig ./tsconfig.json","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":">=8.0.1"},"peerDependencies":{"react":">=16.3.0","react-dom":">=16.3.0","typescript":">=3.9.0"},"devDependencies":{"@commitlint/cli":"^8.1.0","@commitlint/config-conventional":"^8.1.0","@testing-library/react":"^10.0.1","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/faker":"^5.5.3","@types/jest":"^25.1.0","@types/node":"^14.0.0","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","faker":"^5.5.3","husky":"^4.0.2","jest":"^24.1.0","microbundle":"^0.12.3","prettier":"^2.0.0","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^8.0.0","remark-lint":"^7.0.0","remark-preset-lint-recommended":"^4.0.0","ts-jest":"^26.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"browserslist":["edge 17","firefox 70","chrome 48","safari 12.1","android 4.0","samsung 9.2"],"readme":"# react-model &middot; ![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) [![Build Status](https://travis-ci.org/byte-fe/react-model.svg?branch=master)](https://travis-ci.org/byte-fe/react-model) [![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 = () => {\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(Todo)\n\nconst App = () => {\n  return <TodoList />\n}\n\nconst TodoList = () => {\n  const { items, setItems } = useStore()\n  return <div>\n    <Addon handler={setItems} />\n    {state.items.map((item, index) => (<Todo key={index} item={item} />))}\n  </div>\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[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  - [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 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### 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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (payload, { state }) => {\n      return {\n        counter: state.counter + (payload || 1)\n      }\n    },\n    openLight: async (_, { state, actions }) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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<typeof Model.actions>\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 () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button onClick={e => actions.increment(33)}>home increment</button>\n      <button onClick={e => sharedActions.increment(20)}>\n        shared increment\n      </button>\n      <button onClick={e => actions.get()}>fake request</button>\n      <button onClick={e => actions.openLight()}>fake nested call</button>\n    </div>\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 >= 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> Hint: The state returned should only be used as readonly\n\n```jsx\nimport { useStore, getState } from '../model/index'\n\nconst BasicHook = () => {\n  const [state, actions] = useStore('Counter')\n  useEffect(() => {\n    console.log('some mounted actions from BasicHooks')\n    return () =>\n      console.log(\n        `Basic Hooks unmounted, current Counter state: ${JSON.stringify(\n          getState('Counter')\n        )}`\n      )\n  }, [])\n  return (\n    <>\n      <div>state: {JSON.stringify(state)}</div>\n    </>\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: () => {\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 = () => {\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (params, { state: s }) => {\n      // return (state: typeof s) => { // TypeScript < 3.9\n      return state => {\n        state.counter += params || 1\n      }\n    },\n    decrease: params => s => {\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) => {\n      return state => {\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<details>\n<summary>Store: shared.ts</summary>\n<p>\n\n```ts\nconst initialState = {\n  counter: 0\n}\n\nconst model: ModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: (params, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    }\n  },\n  // Provide for SSR\n  asyncState: async context => {\n    await waitFor(4000)\n    return { counter: 500 }\n  },\n  state: initialState\n}\n\nexport default model\n```\n\n</p>\n</details>\n\n<details>\n<summary>Global Config: _app.tsx</summary>\n<p>\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) => {\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    <Container>\n      <Component {...pageProps} />\n    </Container>\n  )\n}\n\nMyApp.getInitialProps = async (context: NextAppContext) => {\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</p>\n</details>\n\n<details>\n<summary>Page: hooks/index.tsx</summary>\n<p>\n\n```tsx\nimport { useStore, getState } from '../index'\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button\n        onClick={e => {\n          actions.increment(33)\n        }}\n      >\n    </div>\n  )\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Single Page Config: benchmark.tsx</summary>\n<p>\n\n```tsx\n// ...\nBenchmark.getInitialProps = async () => {\n  return await getInitialState({ modelName: 'Todo' }, { isServer: true })\n}\n```\n</p>\n</details>\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<{}> = async (context, restMiddlewares) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: any) => 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) => async (params: any) => {\n  const context: Context = {\n    modelName,\n    setState,\n    actionName: action.name,\n    next: () => {},\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<\n  { name: string }, // State Type\n  { ext: undefined }, // ActionParamsType\n  { name: string } // ExtContextType\n> = {\n  actions: {\n    // { state, action } => { state, action, [name] }\n    ext: (_, { name }) => {\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 => '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      <Provider>\n        <Counter />\n      </Provider>\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<details>\n<summary>Javascript decorator version</summary>\n<p>\n\n```jsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\n\nconst mapProps = ({ light, counter }) => ({\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      <>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(5)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n      </>\n    )\n  }\n}\n```\n\n</p>\n</details>\n\n<details>\n<summary>TypeScript Version</summary>\n<p>\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) => ({\n  lightStatus: light ? 'open' : 'close',\n  counter,\n  response\n})\n\ntype RType = ReturnType<typeof mapProps>\n\nclass TSCounter extends PureComponent<\n  { state: RType } & { actions: ActionType }\n> {\n  render() {\n    const { state, actions } = this.props\n    return (\n      <>\n        <div>TS Counter</div>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(3)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n        <button onClick={e => actions.get()}>Get Response</button>\n        <div>message: {JSON.stringify(state.response)}</div>\n      </>\n    )\n  }\n}\n\nexport default connect(\n  'Home',\n  mapProps\n)(TSCounter)\n```\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\n\n## FAQ\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<State, ActionParams> = {\n  state: {\n    counter: 1\n    enable: false\n  },\n  actions: {\n    add: (payload) => state => {\n      state.counter += payload\n    },\n    switch: () => state => {\n      state.enable = !state.enable\n    }\n  }\n}\n```\n\n```ts\nconst Component = () => {\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 => 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 }) => ['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) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: Error) => 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) => {\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      <div className=\"App\">\n        <Counter />\n        <Counter />\n        <Counter />\n      </div>\n    )\n  }\n}\n```\n\n<details>\n<summary>Counter model</summary>\n<p>\n\n```ts\ninterface State {\n  count: number\n}\n\ninterface ActionParams {\n  increment: number\n}\n\nconst model: ModelType<State, ActionParams> = {\n  state: {\n    count: 0\n  },\n  actions: {\n    increment: payload => {\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 => (state.count += payload)\n      return state => {\n        state.count += payload\n      }\n    }\n  }\n}\n\n```\n\n</p>\n</details>\n\n<details>\n<summary>Counter.tsx</summary>\n<p>\n\n```tsx\n\nconst Counter = () => {\n  const [{ useStore }] = useState(() => Model(model))\n  const [state, actions] = useStore()\n  return (\n    <div>\n      <div>{state.count}</div>\n      <button onClick={() => actions.increment(3)}>Increment</button>\n    </div>\n  )\n}\n\nexport default Counter\n```\n\n</p>\n</details>\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<ExpensiveState, ExpensiveActionParams> = {\n  state: {\n    moduleList: []\n  },\n  actions: {\n    setPreFreezedDataset: () => {\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 => {\n    // Not allowed\n    // return state => (state.count += payload)\n    return state => {\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) => {\n  await timeout(1000, {})\n  context.next(restMiddlewares)\n}\n\nconst nextCounterModel: ModelType<CounterState, NextCounterActionParams> = {\n  actions: {\n    add: num => {\n      return state => {\n        state.count += num\n      }\n    },\n    increment: async (num, { actions }) => {\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","readmeFilename":"README.md","gitHead":"ac52e98f9982807c7dca18b276a014640ea8a139","_id":"react-model@4.1.0-alpha.0","_nodeVersion":"12.10.0","_npmVersion":"7.10.0","dist":{"integrity":"sha512-FwygF/dX6mKIEZ7Oq59GOspKc4cWgCf7hzxSbdt1CNRi2WxwgOmlCaaPIMqCIdc34fJrPY53CmSp36mxr0Zb4w==","shasum":"c552b2ac970a77a136aea54cb8fc49e98450973c","tarball":"https://registry.npmjs.org/react-model/-/react-model-4.1.0-alpha.0.tgz","fileCount":22,"unpackedSize":101581,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgzBsFCRA9TVsSAnZWagAAAuYP/2Htpgo6PjNdaAkK6ypj\nz6t8t1pRI7208Ay0Zt/KIQWDeHd+hEmnhmPgX0WckjtKU+8Tgxygj0Iw+eGr\nhgmmOYFnSS2eCAfTeBwo5zzuMw9eciMYAMS16bI88aR7/8baxXWTroLnniIp\nvy1jQubXK8PKZCnF4osFJZNycujmhvXMsMStCWdvBbPT1ANnCrASbG0eTf+P\nyfvC1XU7l6i5eZvQnsKbwJ6h0SgRhDdpPmL7HIXUMKfcLgAV3sO6W0AKSWir\nzdmjrx5t+2uN5I7fC6kZqEujfgwIA3oCHurvxfEY0PWVjok81xZ3QzQGQaua\nMCZK3w7FNvxKDdu7NkCkiwrgWgUqYSNCxxix3JP1rCEn3Uj5HC+3x+D6f5Cy\n5yQUQEqC+QZlA7p3z14gtMiT2Z0QJADHBvWNwEzSIA5dMgbzf/p2rV2P4ml/\nFdnlnyqlaQV3unRoo/2BufA2GusxpbwCRI4RyXIP82uRocdDWBq/HPNfJstJ\nUtnOqEy+gC444Jj9QS7klP27pGbOuS0ni2LjK1duj63gsEECKqpWL5o7SYwS\njeUiQkNZ98EDi8bd/0Y5t0bYYeW3OIkm21rxCJTA0aENqbLtY48SYxNzp/Rs\n9TNZPRTGlgeGlLBBRLfrqhKjb6rEycEamFdSE76yycIn/GzaQxf9oxnDYRlg\n8GwQ\r\n=JB6/\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCiV3gNSpcQZ/aqDRKn/Ua9Ih9pYtYokyj75DmiQjMNvgIgGzUTuj7dRMqY+6vpiDxlIGLzVXn/lrozCCyiTSSa2Cg="}]},"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"maintainers":[{"name":"leecade","email":"leecade@163.com"},{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_4.1.0-alpha.0_1623988997307_0.5513272840608932"},"_hasShrinkwrap":false},"4.1.0-alpha.1":{"name":"react-model","version":"4.1.0-alpha.1","description":"The State management library for React","main":"./dist/react-model.js","module":"./dist/react-model.esm.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build:prod":"microbundle --define process.env.NODE_ENV=production --sourcemap false --jsx React.createElement --output dist --tsconfig ./tsconfig.json","build:dev":"microbundle --define process.env.NODE_ENV=development --sourcemap true --jsx React.createElement --output dist --tsconfig ./tsconfig.json","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":">=8.0.1"},"peerDependencies":{"react":">=16.3.0","react-dom":">=16.3.0","typescript":">=3.9.0"},"devDependencies":{"@commitlint/cli":"^8.1.0","@commitlint/config-conventional":"^8.1.0","@testing-library/react":"^10.0.1","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/faker":"^5.5.3","@types/jest":"^25.1.0","@types/node":"^14.0.0","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","faker":"^5.5.3","husky":"^4.0.2","jest":"^24.1.0","microbundle":"^0.12.3","prettier":"^2.0.0","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^8.0.0","remark-lint":"^7.0.0","remark-preset-lint-recommended":"^4.0.0","ts-jest":"^26.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"browserslist":["edge 17","firefox 70","chrome 48","safari 12.1","android 4.0","samsung 9.2"],"readme":"# react-model &middot; ![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) [![Build Status](https://travis-ci.org/byte-fe/react-model.svg?branch=master)](https://travis-ci.org/byte-fe/react-model) [![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 = () => {\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(Todo)\n\nconst App = () => {\n  return <TodoList />\n}\n\nconst TodoList = () => {\n  const { items, setItems } = useStore()\n  return <div>\n    <Addon handler={setItems} />\n    {state.items.map((item, index) => (<Todo key={index} item={item} />))}\n  </div>\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[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  - [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 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### 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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (payload, { state }) => {\n      return {\n        counter: state.counter + (payload || 1)\n      }\n    },\n    openLight: async (_, { state, actions }) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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<typeof Model.actions>\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 () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button onClick={e => actions.increment(33)}>home increment</button>\n      <button onClick={e => sharedActions.increment(20)}>\n        shared increment\n      </button>\n      <button onClick={e => actions.get()}>fake request</button>\n      <button onClick={e => actions.openLight()}>fake nested call</button>\n    </div>\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 >= 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> Hint: The state returned should only be used as readonly\n\n```jsx\nimport { useStore, getState } from '../model/index'\n\nconst BasicHook = () => {\n  const [state, actions] = useStore('Counter')\n  useEffect(() => {\n    console.log('some mounted actions from BasicHooks')\n    return () =>\n      console.log(\n        `Basic Hooks unmounted, current Counter state: ${JSON.stringify(\n          getState('Counter')\n        )}`\n      )\n  }, [])\n  return (\n    <>\n      <div>state: {JSON.stringify(state)}</div>\n    </>\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: () => {\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 = () => {\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (params, { state: s }) => {\n      // return (state: typeof s) => { // TypeScript < 3.9\n      return state => {\n        state.counter += params || 1\n      }\n    },\n    decrease: params => s => {\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) => {\n      return state => {\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<details>\n<summary>Store: shared.ts</summary>\n<p>\n\n```ts\nconst initialState = {\n  counter: 0\n}\n\nconst model: ModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: (params, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    }\n  },\n  // Provide for SSR\n  asyncState: async context => {\n    await waitFor(4000)\n    return { counter: 500 }\n  },\n  state: initialState\n}\n\nexport default model\n```\n\n</p>\n</details>\n\n<details>\n<summary>Global Config: _app.tsx</summary>\n<p>\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) => {\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    <Container>\n      <Component {...pageProps} />\n    </Container>\n  )\n}\n\nMyApp.getInitialProps = async (context: NextAppContext) => {\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</p>\n</details>\n\n<details>\n<summary>Page: hooks/index.tsx</summary>\n<p>\n\n```tsx\nimport { useStore, getState } from '../index'\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button\n        onClick={e => {\n          actions.increment(33)\n        }}\n      >\n    </div>\n  )\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Single Page Config: benchmark.tsx</summary>\n<p>\n\n```tsx\n// ...\nBenchmark.getInitialProps = async () => {\n  return await getInitialState({ modelName: 'Todo' }, { isServer: true })\n}\n```\n</p>\n</details>\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<{}> = async (context, restMiddlewares) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: any) => 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) => async (params: any) => {\n  const context: Context = {\n    modelName,\n    setState,\n    actionName: action.name,\n    next: () => {},\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<\n  { name: string }, // State Type\n  { ext: undefined }, // ActionParamsType\n  { name: string } // ExtContextType\n> = {\n  actions: {\n    // { state, action } => { state, action, [name] }\n    ext: (_, { name }) => {\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 => '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      <Provider>\n        <Counter />\n      </Provider>\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<details>\n<summary>Javascript decorator version</summary>\n<p>\n\n```jsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\n\nconst mapProps = ({ light, counter }) => ({\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      <>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(5)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n      </>\n    )\n  }\n}\n```\n\n</p>\n</details>\n\n<details>\n<summary>TypeScript Version</summary>\n<p>\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) => ({\n  lightStatus: light ? 'open' : 'close',\n  counter,\n  response\n})\n\ntype RType = ReturnType<typeof mapProps>\n\nclass TSCounter extends PureComponent<\n  { state: RType } & { actions: ActionType }\n> {\n  render() {\n    const { state, actions } = this.props\n    return (\n      <>\n        <div>TS Counter</div>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(3)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n        <button onClick={e => actions.get()}>Get Response</button>\n        <div>message: {JSON.stringify(state.response)}</div>\n      </>\n    )\n  }\n}\n\nexport default connect(\n  'Home',\n  mapProps\n)(TSCounter)\n```\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\n\n## FAQ\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<State, ActionParams> = {\n  state: {\n    counter: 1\n    enable: false\n  },\n  actions: {\n    add: (payload) => state => {\n      state.counter += payload\n    },\n    switch: () => state => {\n      state.enable = !state.enable\n    }\n  }\n}\n```\n\n```ts\nconst Component = () => {\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 => 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 }) => ['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) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: Error) => 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) => {\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      <div className=\"App\">\n        <Counter />\n        <Counter />\n        <Counter />\n      </div>\n    )\n  }\n}\n```\n\n<details>\n<summary>Counter model</summary>\n<p>\n\n```ts\ninterface State {\n  count: number\n}\n\ninterface ActionParams {\n  increment: number\n}\n\nconst model: ModelType<State, ActionParams> = {\n  state: {\n    count: 0\n  },\n  actions: {\n    increment: payload => {\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 => (state.count += payload)\n      return state => {\n        state.count += payload\n      }\n    }\n  }\n}\n\n```\n\n</p>\n</details>\n\n<details>\n<summary>Counter.tsx</summary>\n<p>\n\n```tsx\n\nconst Counter = () => {\n  const [{ useStore }] = useState(() => Model(model))\n  const [state, actions] = useStore()\n  return (\n    <div>\n      <div>{state.count}</div>\n      <button onClick={() => actions.increment(3)}>Increment</button>\n    </div>\n  )\n}\n\nexport default Counter\n```\n\n</p>\n</details>\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<ExpensiveState, ExpensiveActionParams> = {\n  state: {\n    moduleList: []\n  },\n  actions: {\n    setPreFreezedDataset: () => {\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 => {\n    // Not allowed\n    // return state => (state.count += payload)\n    return state => {\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) => {\n  await timeout(1000, {})\n  context.next(restMiddlewares)\n}\n\nconst nextCounterModel: ModelType<CounterState, NextCounterActionParams> = {\n  actions: {\n    add: num => {\n      return state => {\n        state.count += num\n      }\n    },\n    increment: async (num, { actions }) => {\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","readmeFilename":"README.md","gitHead":"6c11b13a6b86f4ca239fdeb2b63745957c6f46f5","_id":"react-model@4.1.0-alpha.1","_nodeVersion":"12.10.0","_npmVersion":"7.10.0","dist":{"integrity":"sha512-07nHAb+P8uDj3FADm6Vm9SsFZoWAiLUN6L58bw3f6vzfKE5FJfKPHD4vgSVgxCAgPrf7CmDvO2FXgg4cxnYqyg==","shasum":"3d3cb578cbdd7b0892e7739ec1fd2efc2682a34d","tarball":"https://registry.npmjs.org/react-model/-/react-model-4.1.0-alpha.1.tgz","fileCount":22,"unpackedSize":102035,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgzFQdCRA9TVsSAnZWagAAIZ8P/iBm+enuhal8vP9Iijqn\n2+vk83pzX6UhMJUd3Vogzh50EhlJtAvr9g2QASJrx/XMBGV/oLXtdCavAoLg\n3pRrPZq2n24Vpo0iDQ/gXomKoJd5iAuZ+yI11IgyISsQCJo+6BmyR6v+mdkS\nLvx/bGuBKqDnHONtL3cdBjh8k6o2Ne/bjytqyX2+HU0eR8OwuM5E9VoS0PNh\nqEG3uFYaNfKtfvgEUFPqInjy4yBr9gUcRh2Dglzy8as1EBUqbFSAwb03niXI\n5QUrd0cBhoI668qhnZ93UQn3x2Z17FecWb9Bcsiytliav4BHabPAKp4Yj10v\nq134P/TbECMPt0z7APotvopEtJJaS/NEbKqsYWa3KFoN/SqWlhvzyyB6lc+N\n7jS1cyoUUgW5ejUk5ypBBEOYFHgf1oeEudstxICIb+5cPrA2PPTmoqhWH2Sr\nRed/mCjKRVIxNP77GOFLRIM7GWuW6WENk0ReSo4FTeNaFxT/WtBOVD/7wUMx\nR4UiQb+6SHZR94gE5idJlP7Iu+K1DGbtjWpZSQEwjM+g2rM/l0U4o7oO76n4\n9fuAGu0/1ptXSd9gNxXemiXlirn6BVLRwaScyvHTrqF3ZbZQoSZU2Ohcz0XB\ny11O7sqL+VP1JR/pLEZjwmunXLyJ+YXED7Vtdt4jfG6rtO6RnSAYKAkh2P1u\n1XrX\r\n=qgnu\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQCLSqXC58cPPfv7XjNqtqIzBa7pnwDUnl7ZCyUHH55C8wIhAN5WWGEel363mGfvH4YNUYlt1VSEwMCq5sRlzeMHF1Lz"}]},"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"maintainers":[{"name":"leecade","email":"leecade@163.com"},{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_4.1.0-alpha.1_1624003613003_0.6434187076213387"},"_hasShrinkwrap":false},"4.1.0-alpha.2":{"name":"react-model","version":"4.1.0-alpha.2","description":"The State management library for React","main":"./dist/react-model.js","module":"./dist/react-model.esm.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build:prod":"microbundle --define process.env.NODE_ENV=production --sourcemap false --jsx React.createElement --output dist --tsconfig ./tsconfig.json","build:dev":"microbundle --define process.env.NODE_ENV=development --sourcemap true --jsx React.createElement --output dist --tsconfig ./tsconfig.json","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":">=8.0.1"},"peerDependencies":{"react":">=16.3.0","react-dom":">=16.3.0","typescript":">=3.9.0"},"devDependencies":{"@commitlint/cli":"^8.1.0","@commitlint/config-conventional":"^8.1.0","@testing-library/react":"^10.0.1","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/faker":"^5.5.3","@types/jest":"^25.1.0","@types/node":"^14.0.0","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","faker":"^5.5.3","husky":"^4.0.2","jest":"^24.1.0","microbundle":"^0.12.3","prettier":"^2.0.0","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^8.0.0","remark-lint":"^7.0.0","remark-preset-lint-recommended":"^4.0.0","ts-jest":"^26.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"browserslist":["edge 17","firefox 70","chrome 48","safari 12.1","android 4.0","samsung 9.2"],"readme":"# react-model &middot; ![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) [![Build Status](https://travis-ci.org/byte-fe/react-model.svg?branch=master)](https://travis-ci.org/byte-fe/react-model) [![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 = () => {\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(Todo)\n\nconst App = () => {\n  return <TodoList />\n}\n\nconst TodoList = () => {\n  const { items, setItems } = useStore()\n  return <div>\n    <Addon handler={setItems} />\n    {state.items.map((item, index) => (<Todo key={index} item={item} />))}\n  </div>\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[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  - [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 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### 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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (payload, { state }) => {\n      return {\n        counter: state.counter + (payload || 1)\n      }\n    },\n    openLight: async (_, { state, actions }) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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<typeof Model.actions>\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 () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button onClick={e => actions.increment(33)}>home increment</button>\n      <button onClick={e => sharedActions.increment(20)}>\n        shared increment\n      </button>\n      <button onClick={e => actions.get()}>fake request</button>\n      <button onClick={e => actions.openLight()}>fake nested call</button>\n    </div>\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 >= 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> Hint: The state returned should only be used as readonly\n\n```jsx\nimport { useStore, getState } from '../model/index'\n\nconst BasicHook = () => {\n  const [state, actions] = useStore('Counter')\n  useEffect(() => {\n    console.log('some mounted actions from BasicHooks')\n    return () =>\n      console.log(\n        `Basic Hooks unmounted, current Counter state: ${JSON.stringify(\n          getState('Counter')\n        )}`\n      )\n  }, [])\n  return (\n    <>\n      <div>state: {JSON.stringify(state)}</div>\n    </>\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: () => {\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 = () => {\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (params, { state: s }) => {\n      // return (state: typeof s) => { // TypeScript < 3.9\n      return state => {\n        state.counter += params || 1\n      }\n    },\n    decrease: params => s => {\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) => {\n      return state => {\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<details>\n<summary>Store: shared.ts</summary>\n<p>\n\n```ts\nconst initialState = {\n  counter: 0\n}\n\nconst model: ModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: (params, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    }\n  },\n  // Provide for SSR\n  asyncState: async context => {\n    await waitFor(4000)\n    return { counter: 500 }\n  },\n  state: initialState\n}\n\nexport default model\n```\n\n</p>\n</details>\n\n<details>\n<summary>Global Config: _app.tsx</summary>\n<p>\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) => {\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    <Container>\n      <Component {...pageProps} />\n    </Container>\n  )\n}\n\nMyApp.getInitialProps = async (context: NextAppContext) => {\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</p>\n</details>\n\n<details>\n<summary>Page: hooks/index.tsx</summary>\n<p>\n\n```tsx\nimport { useStore, getState } from '../index'\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button\n        onClick={e => {\n          actions.increment(33)\n        }}\n      >\n    </div>\n  )\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Single Page Config: benchmark.tsx</summary>\n<p>\n\n```tsx\n// ...\nBenchmark.getInitialProps = async () => {\n  return await getInitialState({ modelName: 'Todo' }, { isServer: true })\n}\n```\n</p>\n</details>\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<{}> = async (context, restMiddlewares) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: any) => 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) => async (params: any) => {\n  const context: Context = {\n    modelName,\n    setState,\n    actionName: action.name,\n    next: () => {},\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<\n  { name: string }, // State Type\n  { ext: undefined }, // ActionParamsType\n  { name: string } // ExtContextType\n> = {\n  actions: {\n    // { state, action } => { state, action, [name] }\n    ext: (_, { name }) => {\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 => '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      <Provider>\n        <Counter />\n      </Provider>\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<details>\n<summary>Javascript decorator version</summary>\n<p>\n\n```jsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\n\nconst mapProps = ({ light, counter }) => ({\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      <>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(5)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n      </>\n    )\n  }\n}\n```\n\n</p>\n</details>\n\n<details>\n<summary>TypeScript Version</summary>\n<p>\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) => ({\n  lightStatus: light ? 'open' : 'close',\n  counter,\n  response\n})\n\ntype RType = ReturnType<typeof mapProps>\n\nclass TSCounter extends PureComponent<\n  { state: RType } & { actions: ActionType }\n> {\n  render() {\n    const { state, actions } = this.props\n    return (\n      <>\n        <div>TS Counter</div>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(3)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n        <button onClick={e => actions.get()}>Get Response</button>\n        <div>message: {JSON.stringify(state.response)}</div>\n      </>\n    )\n  }\n}\n\nexport default connect(\n  'Home',\n  mapProps\n)(TSCounter)\n```\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\n\n## FAQ\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<State, ActionParams> = {\n  state: {\n    counter: 1\n    enable: false\n  },\n  actions: {\n    add: (payload) => state => {\n      state.counter += payload\n    },\n    switch: () => state => {\n      state.enable = !state.enable\n    }\n  }\n}\n```\n\n```ts\nconst Component = () => {\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 => 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 }) => ['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) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: Error) => 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) => {\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      <div className=\"App\">\n        <Counter />\n        <Counter />\n        <Counter />\n      </div>\n    )\n  }\n}\n```\n\n<details>\n<summary>Counter model</summary>\n<p>\n\n```ts\ninterface State {\n  count: number\n}\n\ninterface ActionParams {\n  increment: number\n}\n\nconst model: ModelType<State, ActionParams> = {\n  state: {\n    count: 0\n  },\n  actions: {\n    increment: payload => {\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 => (state.count += payload)\n      return state => {\n        state.count += payload\n      }\n    }\n  }\n}\n\n```\n\n</p>\n</details>\n\n<details>\n<summary>Counter.tsx</summary>\n<p>\n\n```tsx\n\nconst Counter = () => {\n  const [{ useStore }] = useState(() => Model(model))\n  const [state, actions] = useStore()\n  return (\n    <div>\n      <div>{state.count}</div>\n      <button onClick={() => actions.increment(3)}>Increment</button>\n    </div>\n  )\n}\n\nexport default Counter\n```\n\n</p>\n</details>\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<ExpensiveState, ExpensiveActionParams> = {\n  state: {\n    moduleList: []\n  },\n  actions: {\n    setPreFreezedDataset: () => {\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 => {\n    // Not allowed\n    // return state => (state.count += payload)\n    return state => {\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) => {\n  await timeout(1000, {})\n  context.next(restMiddlewares)\n}\n\nconst nextCounterModel: ModelType<CounterState, NextCounterActionParams> = {\n  actions: {\n    add: num => {\n      return state => {\n        state.count += num\n      }\n    },\n    increment: async (num, { actions }) => {\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","readmeFilename":"README.md","gitHead":"007fb79e441aa5a109c694dc554cae7523e4c966","_id":"react-model@4.1.0-alpha.2","_nodeVersion":"12.10.0","_npmVersion":"7.10.0","dist":{"integrity":"sha512-1APgcC3yfm2mnJ93SXwlLjt8N+ZpEToeS7DgXDom0B4yb0RpfJZ0hjgnkgI6Kf59TIZ1Z/CczOkME/PX9Qxscw==","shasum":"a2840434ea8688a08c6e7f4a9ef810021a912c4b","tarball":"https://registry.npmjs.org/react-model/-/react-model-4.1.0-alpha.2.tgz","fileCount":22,"unpackedSize":102313,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg0H0sCRA9TVsSAnZWagAAJrUP+wSy0T5egI9wNYnQm5S+\n7zs/17RU9UbOglgvPy1PiFgcAbOB2P88WJixCSkS0L0VHyW1vLNREV87+ACs\ndwE6V3i7lQFh4bE5JHotGGl/Gh2Pet0C+X98bVGnrzjDpiNCO4IwYbeVv0r+\npS4vhKagA2tT+DYx4YrIFuvl8SaKBPLjfKaDPOA/avgApguvi+4MAt4cOb1m\nu7WSHIrhsFLDlwZKURNSYaPJsRGybiZ7I03sOa2FC5di+mYWUCN/rZKinMwp\nAC9wKKL/gjLok5fpoFS1cWNwDu4V1jKP/oC6jRdUdt6VeB2LlEZuGui4gvH5\nUF2KwD21WrURfrNgFIcGHHXy35v0WYEqPS3BF/1QIZ9+tb6ZZpvT7RKrRNdb\nMcALtreFjhjnbutdmJU2dTjNZY5UOB1xwpNn8g57FKMQUF5oksiQZdNngvhJ\nyl/1AbCkw9aQndvCVq575tMzigaYkbjw/xI0xP4MbcTlORMhQDkIfCD8Tfk5\nJ0SNFco1FuZhCo4QLowpOC/aps8Bu60Onb1F0JYOLib0ge+NZJ18v6KP3oAe\n2mCP5YDgfjTNBhHfp65mosIeov0cEqucJ0FSmVXr8wXaKoP93ple9UcUXyfy\nRM/h4Z2L3XaWG5LJI5H3uxD1z4t6dLx7QfMUhUoefrDfeU6nL265Rc/quj5t\nfg8m\r\n=MqaZ\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQD2TpL7IzvMOo0pmPDc2GH95wLHpx1Juu3e/mI2zn7I9gIhAJ5MA81zrc+h5v1QFfUiwLMJrzlC1LxQK5cla/QvH0SF"}]},"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"maintainers":[{"name":"leecade","email":"leecade@163.com"},{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_4.1.0-alpha.2_1624276267749_0.06503678048772521"},"_hasShrinkwrap":false},"4.0.2":{"name":"react-model","version":"4.0.2","description":"The State management library for React","main":"./dist/react-model.js","module":"./dist/react-model.esm.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build:prod":"microbundle --define process.env.NODE_ENV=production --sourcemap false --jsx React.createElement --output dist --tsconfig ./tsconfig.json","build:dev":"microbundle --define process.env.NODE_ENV=development --sourcemap true --jsx React.createElement --output dist --tsconfig ./tsconfig.json","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":">=8.0.1"},"peerDependencies":{"react":">=16.3.0","react-dom":">=16.3.0","typescript":">=3.9.0"},"devDependencies":{"@commitlint/cli":"^8.1.0","@commitlint/config-conventional":"^8.1.0","@testing-library/react":"^10.0.1","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/faker":"^5.5.3","@types/jest":"^25.1.0","@types/node":"^14.0.0","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","faker":"^5.5.3","husky":"^4.0.2","jest":"^24.1.0","microbundle":"^0.12.3","prettier":"^2.0.0","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^8.0.0","remark-lint":"^7.0.0","remark-preset-lint-recommended":"^4.0.0","ts-jest":"^26.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"browserslist":["edge 17","firefox 70","chrome 48","safari 12.1","android 4.0","samsung 9.2"],"gitHead":"ffb11fa1387fa931499df171833e515eebc86c59","_id":"react-model@4.0.2","_nodeVersion":"12.10.0","_npmVersion":"7.10.0","dist":{"integrity":"sha512-PIlqtKY2G/d8MpU6z6CLxb1lhTxhJW/xNdjQ/EWwDHSzktjo8QWqljXgGaBLHct02OS7P19p7NVym6MVUdFdGA==","shasum":"aa45bcb18c55bc50b0c3c2a825a1afb8f1235419","tarball":"https://registry.npmjs.org/react-model/-/react-model-4.0.2.tgz","fileCount":22,"unpackedSize":96396,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg1UhgCRA9TVsSAnZWagAA2Q0P/1EUDIUuOBZxIMbblen5\njvwsHDVOwnve4H2lwAcTvuwai463CUKOu6+FjLdfbliBMrlCv6UOw5ii0RCG\ngA4tCU95pbwKIptsmWPoKPi7SchnerZTJyhBzherkiyGAABt0gIG1UncwcYV\ncWSujntkkV1LLMkqZ+fIdYyOj8zCJ74Gflr/WGohCm2VKq43qezst+vh58w3\nEfFPcq5kjYzjVHDJnj4CYfxwfwFJ+a4KME+fsP5V7yp3jdr76+UJly9xqC9q\nJ9cKQsYc+wWaiwLNK1+qafZ3zGbwcVX2K8tL1tdDi2mSeiWrx2do2DscnqL9\nSNp1VMycurBhCVUSxWblrgKKmuFe3//+8RzGuSysZUH8SvVZw9+FgJiA4ftV\nmqWz63olQx90s0VL61wurxRrCrts46ZRKJ9rSR2iortQi75V2ch+AvHgUoiN\nUQ+J3739CYdJjl6Ny9yjo0tC/Bx8ubqizscfmZvcwdtZdEW7FVvLhnwAB8ys\nr9NnQSsRPFGRRuwf7sK0CqakxR4WmK/CfOX1HCr+pUCj9ifHqubmfnKQaeby\nF0mguVPUVRnrzvBLqpe13RDSWP4iLA6RJL+cZqiOjtzrirTOyKxB63AgylNI\n2uk42MGrBV7hlTMf7wMM9OcqQvrs1FQhXVy83SxhVgNxIkoi/pZ9X+4Tyk16\nx1VD\r\n=yXQr\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDatVBjo75DFQL+MtzWc+KQQF130DKaK/whrj8b84zYSAIgJ9DQnDsAyHj9JsF0Bv8ult8S5PSVMmsOBh7SWSx3ZxI="}]},"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"maintainers":[{"name":"leecade","email":"leecade@163.com"},{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_4.0.2_1624590432244_0.5971380856151036"},"_hasShrinkwrap":false},"4.1.0-alpha.3":{"name":"react-model","version":"4.1.0-alpha.3","description":"The State management library for React","main":"./dist/react-model.js","module":"./dist/react-model.esm.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build:prod":"microbundle --define process.env.NODE_ENV=production --sourcemap false --jsx React.createElement --output dist --tsconfig ./tsconfig.json","build:dev":"microbundle --define process.env.NODE_ENV=development --sourcemap true --jsx React.createElement --output dist --tsconfig ./tsconfig.json","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":">=8.0.1"},"peerDependencies":{"react":">=16.3.0","react-dom":">=16.3.0","typescript":">=3.9.0"},"devDependencies":{"@commitlint/cli":"^8.1.0","@commitlint/config-conventional":"^8.1.0","@testing-library/react":"^10.0.1","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/faker":"^5.5.3","@types/jest":"^25.1.0","@types/node":"^14.0.0","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","faker":"^5.5.3","husky":"^4.0.2","jest":"^24.1.0","microbundle":"^0.12.3","prettier":"^2.0.0","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^8.0.0","remark-lint":"^7.0.0","remark-preset-lint-recommended":"^4.0.0","ts-jest":"^26.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"browserslist":["edge 17","firefox 70","chrome 48","safari 12.1","android 4.0","samsung 9.2"],"readme":"# react-model &middot; ![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) [![Build Status](https://travis-ci.org/byte-fe/react-model.svg?branch=master)](https://travis-ci.org/byte-fe/react-model) [![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 = () => {\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(Todo)\n\nconst App = () => {\n  return <TodoList />\n}\n\nconst TodoList = () => {\n  const { items, setItems } = useStore()\n  return <div>\n    <Addon handler={setItems} />\n    {state.items.map((item, index) => (<Todo key={index} item={item} />))}\n  </div>\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[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  - [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 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### 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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (payload, { state }) => {\n      return {\n        counter: state.counter + (payload || 1)\n      }\n    },\n    openLight: async (_, { state, actions }) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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<typeof Model.actions>\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 () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button onClick={e => actions.increment(33)}>home increment</button>\n      <button onClick={e => sharedActions.increment(20)}>\n        shared increment\n      </button>\n      <button onClick={e => actions.get()}>fake request</button>\n      <button onClick={e => actions.openLight()}>fake nested call</button>\n    </div>\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 >= 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> Hint: The state returned should only be used as readonly\n\n```jsx\nimport { useStore, getState } from '../model/index'\n\nconst BasicHook = () => {\n  const [state, actions] = useStore('Counter')\n  useEffect(() => {\n    console.log('some mounted actions from BasicHooks')\n    return () =>\n      console.log(\n        `Basic Hooks unmounted, current Counter state: ${JSON.stringify(\n          getState('Counter')\n        )}`\n      )\n  }, [])\n  return (\n    <>\n      <div>state: {JSON.stringify(state)}</div>\n    </>\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: () => {\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 = () => {\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (params, { state: s }) => {\n      // return (state: typeof s) => { // TypeScript < 3.9\n      return state => {\n        state.counter += params || 1\n      }\n    },\n    decrease: params => s => {\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) => {\n      return state => {\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<details>\n<summary>Store: shared.ts</summary>\n<p>\n\n```ts\nconst initialState = {\n  counter: 0\n}\n\nconst model: ModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: (params, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    }\n  },\n  // Provide for SSR\n  asyncState: async context => {\n    await waitFor(4000)\n    return { counter: 500 }\n  },\n  state: initialState\n}\n\nexport default model\n```\n\n</p>\n</details>\n\n<details>\n<summary>Global Config: _app.tsx</summary>\n<p>\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) => {\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    <Container>\n      <Component {...pageProps} />\n    </Container>\n  )\n}\n\nMyApp.getInitialProps = async (context: NextAppContext) => {\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</p>\n</details>\n\n<details>\n<summary>Page: hooks/index.tsx</summary>\n<p>\n\n```tsx\nimport { useStore, getState } from '../index'\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button\n        onClick={e => {\n          actions.increment(33)\n        }}\n      >\n    </div>\n  )\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Single Page Config: benchmark.tsx</summary>\n<p>\n\n```tsx\n// ...\nBenchmark.getInitialProps = async () => {\n  return await getInitialState({ modelName: 'Todo' }, { isServer: true })\n}\n```\n</p>\n</details>\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<{}> = async (context, restMiddlewares) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: any) => 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) => async (params: any) => {\n  const context: Context = {\n    modelName,\n    setState,\n    actionName: action.name,\n    next: () => {},\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<\n  { name: string }, // State Type\n  { ext: undefined }, // ActionParamsType\n  { name: string } // ExtContextType\n> = {\n  actions: {\n    // { state, action } => { state, action, [name] }\n    ext: (_, { name }) => {\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 => '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      <Provider>\n        <Counter />\n      </Provider>\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<details>\n<summary>Javascript decorator version</summary>\n<p>\n\n```jsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\n\nconst mapProps = ({ light, counter }) => ({\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      <>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(5)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n      </>\n    )\n  }\n}\n```\n\n</p>\n</details>\n\n<details>\n<summary>TypeScript Version</summary>\n<p>\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) => ({\n  lightStatus: light ? 'open' : 'close',\n  counter,\n  response\n})\n\ntype RType = ReturnType<typeof mapProps>\n\nclass TSCounter extends PureComponent<\n  { state: RType } & { actions: ActionType }\n> {\n  render() {\n    const { state, actions } = this.props\n    return (\n      <>\n        <div>TS Counter</div>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(3)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n        <button onClick={e => actions.get()}>Get Response</button>\n        <div>message: {JSON.stringify(state.response)}</div>\n      </>\n    )\n  }\n}\n\nexport default connect(\n  'Home',\n  mapProps\n)(TSCounter)\n```\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\n\n## FAQ\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<State, ActionParams> = {\n  state: {\n    counter: 1\n    enable: false\n  },\n  actions: {\n    add: (payload) => state => {\n      state.counter += payload\n    },\n    switch: () => state => {\n      state.enable = !state.enable\n    }\n  }\n}\n```\n\n```ts\nconst Component = () => {\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 => 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 }) => ['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) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: Error) => 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) => {\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      <div className=\"App\">\n        <Counter />\n        <Counter />\n        <Counter />\n      </div>\n    )\n  }\n}\n```\n\n<details>\n<summary>Counter model</summary>\n<p>\n\n```ts\ninterface State {\n  count: number\n}\n\ninterface ActionParams {\n  increment: number\n}\n\nconst model: ModelType<State, ActionParams> = {\n  state: {\n    count: 0\n  },\n  actions: {\n    increment: payload => {\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 => (state.count += payload)\n      return state => {\n        state.count += payload\n      }\n    }\n  }\n}\n\n```\n\n</p>\n</details>\n\n<details>\n<summary>Counter.tsx</summary>\n<p>\n\n```tsx\n\nconst Counter = () => {\n  const [{ useStore }] = useState(() => Model(model))\n  const [state, actions] = useStore()\n  return (\n    <div>\n      <div>{state.count}</div>\n      <button onClick={() => actions.increment(3)}>Increment</button>\n    </div>\n  )\n}\n\nexport default Counter\n```\n\n</p>\n</details>\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<ExpensiveState, ExpensiveActionParams> = {\n  state: {\n    moduleList: []\n  },\n  actions: {\n    setPreFreezedDataset: () => {\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 => {\n    // Not allowed\n    // return state => (state.count += payload)\n    return state => {\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) => {\n  await timeout(1000, {})\n  context.next(restMiddlewares)\n}\n\nconst nextCounterModel: ModelType<CounterState, NextCounterActionParams> = {\n  actions: {\n    add: num => {\n      return state => {\n        state.count += num\n      }\n    },\n    increment: async (num, { actions }) => {\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","readmeFilename":"README.md","gitHead":"2559925a5f00925631b79b0cd9c9ebde8b9a5534","_id":"react-model@4.1.0-alpha.3","_nodeVersion":"12.22.1","_npmVersion":"7.19.0","dist":{"integrity":"sha512-BxxewBoJq3epcY71HCoIzK8XtJiQjw6XOoQruegswrdsxusYWBoKVhb8zKnE+HbSxM1E/8L3vaG9gEDl0T06JA==","shasum":"50c59f7a6e8d86d2c0f45681032a44a4e1bf8fb7","tarball":"https://registry.npmjs.org/react-model/-/react-model-4.1.0-alpha.3.tgz","fileCount":22,"unpackedSize":105734,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg2c43CRA9TVsSAnZWagAAx6kP/1e5/nUsVotiCkpZw6im\nDMwTZFBmbP2hFPyVA9kZw49OWS23AiKM/6YT+NpG2Eonzs24ymsJGn0XInAt\nnp9eVRAKJriPiLXRBux8/YRuIZnK/u+/JgbWwck4zmpgNzBRO/2YAm7AgbwJ\nZQ1DJ5nmE360uu41Z+GuVPV0oKmHuSNvVS4+UlEjucsLf5c8gnFZ1PyGD17n\nDbdb927IYe8MFgOly0tF2JDZNoAk8mrkHfmk75KVoltMuupEOyEXtWt1cZXi\nP0vOUHCDq88OQ6HLMO4dcoeaalU76RP55D9ci94FWtbkqhN59udMSQfjqYNI\n4/JPe8YpEgUM6yipDnuuyImCnz5UKHI2mP2fSW6UrCenEvdsFLlGU495xbBs\n4AkCOvN2bLfvwMvqIQFLvv9Bgh3cW+Z7dJA7pTWPMZa2mMLIrv7Icgzea2Ur\ngScOOBRET1oiTG9kg/mi5wNhteVZ7Usrm5/4KRF4DhhBkxzozEm9LS6VFYh9\nyxgow/2qm81RPKAHZBzxciUGY8JAEd9+gg4ZS0uddVHI2Af/AHwuU9XEgvSZ\nIjKkS7riZj2th3poY1W5jhnCF7SrztsgyLldKabSCi7dyb2KutLbTmNk1gJ+\nitplD2hnCY5z/ZNuiVF1K9UIyVCpWkTwxZUxRIP9rdSVm25huI0XUSZs3xeK\noIyJ\r\n=asLa\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDWP/E9hMpsbDk0UUv7Apn6VRsUApTmhLwl1tLJ3/c5UAIgGPN5AC1CKseqlagJvOpWrzDTZvZlbUxhWkXodNQu218="}]},"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"maintainers":[{"name":"leecade","email":"leecade@163.com"},{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_4.1.0-alpha.3_1624886839263_0.8801709715664208"},"_hasShrinkwrap":false},"4.1.0-rc.0":{"name":"react-model","version":"4.1.0-rc.0","description":"The State management library for React","main":"./dist/react-model.js","module":"./dist/react-model.esm.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build:prod":"microbundle --define process.env.NODE_ENV=production --sourcemap false --jsx React.createElement --output dist --tsconfig ./tsconfig.json","build:dev":"microbundle --define process.env.NODE_ENV=development --sourcemap true --jsx React.createElement --output dist --tsconfig ./tsconfig.json","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":">=8.0.1"},"peerDependencies":{"react":">=16.3.0","react-dom":">=16.3.0","typescript":">=3.9.0"},"devDependencies":{"@commitlint/cli":"^8.1.0","@commitlint/config-conventional":"^8.1.0","@testing-library/react":"^10.0.1","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/faker":"^5.5.3","@types/jest":"^25.1.0","@types/node":"^14.0.0","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","faker":"^5.5.3","husky":"^4.0.2","jest":"^24.1.0","microbundle":"^0.12.3","prettier":"^2.0.0","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^8.0.0","remark-lint":"^7.0.0","remark-preset-lint-recommended":"^4.0.0","ts-jest":"^26.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"browserslist":["edge 17","firefox 70","chrome 48","safari 12.1","android 4.0","samsung 9.2"],"readme":"# react-model &middot; ![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) [![Build Status](https://travis-ci.org/byte-fe/react-model.svg?branch=master)](https://travis-ci.org/byte-fe/react-model) [![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 = () => {\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(Todo)\n\nconst App = () => {\n  return <TodoList />\n}\n\nconst TodoList = () => {\n  const { items, setItems } = useStore()\n  return <div>\n    <Addon handler={setItems} />\n    {state.items.map((item, index) => (<Todo key={index} item={item} />))}\n  </div>\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[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  - [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 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### 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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (payload, { state }) => {\n      return {\n        counter: state.counter + (payload || 1)\n      }\n    },\n    openLight: async (_, { state, actions }) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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<typeof Model.actions>\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 () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button onClick={e => actions.increment(33)}>home increment</button>\n      <button onClick={e => sharedActions.increment(20)}>\n        shared increment\n      </button>\n      <button onClick={e => actions.get()}>fake request</button>\n      <button onClick={e => actions.openLight()}>fake nested call</button>\n    </div>\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 >= 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> Hint: The state returned should only be used as readonly\n\n```jsx\nimport { useStore, getState } from '../model/index'\n\nconst BasicHook = () => {\n  const [state, actions] = useStore('Counter')\n  useEffect(() => {\n    console.log('some mounted actions from BasicHooks')\n    return () =>\n      console.log(\n        `Basic Hooks unmounted, current Counter state: ${JSON.stringify(\n          getState('Counter')\n        )}`\n      )\n  }, [])\n  return (\n    <>\n      <div>state: {JSON.stringify(state)}</div>\n    </>\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: () => {\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 = () => {\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (params, { state: s }) => {\n      // return (state: typeof s) => { // TypeScript < 3.9\n      return state => {\n        state.counter += params || 1\n      }\n    },\n    decrease: params => s => {\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) => {\n      return state => {\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<details>\n<summary>Store: shared.ts</summary>\n<p>\n\n```ts\nconst initialState = {\n  counter: 0\n}\n\nconst model: ModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: (params, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    }\n  },\n  // Provide for SSR\n  asyncState: async context => {\n    await waitFor(4000)\n    return { counter: 500 }\n  },\n  state: initialState\n}\n\nexport default model\n```\n\n</p>\n</details>\n\n<details>\n<summary>Global Config: _app.tsx</summary>\n<p>\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) => {\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    <Container>\n      <Component {...pageProps} />\n    </Container>\n  )\n}\n\nMyApp.getInitialProps = async (context: NextAppContext) => {\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</p>\n</details>\n\n<details>\n<summary>Page: hooks/index.tsx</summary>\n<p>\n\n```tsx\nimport { useStore, getState } from '../index'\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button\n        onClick={e => {\n          actions.increment(33)\n        }}\n      >\n    </div>\n  )\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Single Page Config: benchmark.tsx</summary>\n<p>\n\n```tsx\n// ...\nBenchmark.getInitialProps = async () => {\n  return await getInitialState({ modelName: 'Todo' }, { isServer: true })\n}\n```\n</p>\n</details>\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<{}> = async (context, restMiddlewares) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: any) => 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) => async (params: any) => {\n  const context: Context = {\n    modelName,\n    setState,\n    actionName: action.name,\n    next: () => {},\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<\n  { name: string }, // State Type\n  { ext: undefined }, // ActionParamsType\n  { name: string } // ExtContextType\n> = {\n  actions: {\n    // { state, action } => { state, action, [name] }\n    ext: (_, { name }) => {\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 => '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      <Provider>\n        <Counter />\n      </Provider>\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<details>\n<summary>Javascript decorator version</summary>\n<p>\n\n```jsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\n\nconst mapProps = ({ light, counter }) => ({\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      <>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(5)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n      </>\n    )\n  }\n}\n```\n\n</p>\n</details>\n\n<details>\n<summary>TypeScript Version</summary>\n<p>\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) => ({\n  lightStatus: light ? 'open' : 'close',\n  counter,\n  response\n})\n\ntype RType = ReturnType<typeof mapProps>\n\nclass TSCounter extends PureComponent<\n  { state: RType } & { actions: ActionType }\n> {\n  render() {\n    const { state, actions } = this.props\n    return (\n      <>\n        <div>TS Counter</div>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(3)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n        <button onClick={e => actions.get()}>Get Response</button>\n        <div>message: {JSON.stringify(state.response)}</div>\n      </>\n    )\n  }\n}\n\nexport default connect(\n  'Home',\n  mapProps\n)(TSCounter)\n```\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\n\n## FAQ\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<State, ActionParams> = {\n  state: {\n    counter: 1\n    enable: false\n  },\n  actions: {\n    add: (payload) => state => {\n      state.counter += payload\n    },\n    switch: () => state => {\n      state.enable = !state.enable\n    }\n  }\n}\n```\n\n```ts\nconst Component = () => {\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 => 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 }) => ['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) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: Error) => 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) => {\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      <div className=\"App\">\n        <Counter />\n        <Counter />\n        <Counter />\n      </div>\n    )\n  }\n}\n```\n\n<details>\n<summary>Counter model</summary>\n<p>\n\n```ts\ninterface State {\n  count: number\n}\n\ninterface ActionParams {\n  increment: number\n}\n\nconst model: ModelType<State, ActionParams> = {\n  state: {\n    count: 0\n  },\n  actions: {\n    increment: payload => {\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 => (state.count += payload)\n      return state => {\n        state.count += payload\n      }\n    }\n  }\n}\n\n```\n\n</p>\n</details>\n\n<details>\n<summary>Counter.tsx</summary>\n<p>\n\n```tsx\n\nconst Counter = () => {\n  const [{ useStore }] = useState(() => Model(model))\n  const [state, actions] = useStore()\n  return (\n    <div>\n      <div>{state.count}</div>\n      <button onClick={() => actions.increment(3)}>Increment</button>\n    </div>\n  )\n}\n\nexport default Counter\n```\n\n</p>\n</details>\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<ExpensiveState, ExpensiveActionParams> = {\n  state: {\n    moduleList: []\n  },\n  actions: {\n    setPreFreezedDataset: () => {\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 => {\n    // Not allowed\n    // return state => (state.count += payload)\n    return state => {\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) => {\n  await timeout(1000, {})\n  context.next(restMiddlewares)\n}\n\nconst nextCounterModel: ModelType<CounterState, NextCounterActionParams> = {\n  actions: {\n    add: num => {\n      return state => {\n        state.count += num\n      }\n    },\n    increment: async (num, { actions }) => {\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","readmeFilename":"README.md","gitHead":"2559925a5f00925631b79b0cd9c9ebde8b9a5534","_id":"react-model@4.1.0-rc.0","_nodeVersion":"14.8.0","_npmVersion":"6.14.7","dist":{"integrity":"sha512-3w8cGv/DK0j6NA4DzFkg730F91bSpAsysDfqgXZX6a/T7a/1yu+hTVfKvvVrbEFgaBler1i7ysXjO0vvtHjy9g==","shasum":"843e9cb1c790f6042882905f0fd60fbca543f869","tarball":"https://registry.npmjs.org/react-model/-/react-model-4.1.0-rc.0.tgz","fileCount":22,"unpackedSize":105731,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg4w9pCRA9TVsSAnZWagAAeasP/1pW7ilB+QlHle3wiJ2z\nw5fUKflOwjFl0MmYJsmRG5bLnkGXDSexc7TvrEwyr1T1i819PJxgCJNH027H\nMAJAROjEB0VoyxxlFePWGuwaghNhKC3pNYDPUjVp44Z/JXr3zk4dKgiDdvQh\nOzqjjnaqT1S6JkQByCnUtQB56DnunYjxrYoAlz1VtMT+Gs0BLWoSGLxnju8H\n/q4lAUZ3tdb/+qbXXOLXNWodapDfNXJSbBfcoP5u/dF89iKfKGTQ3Z/FWF4s\ns5o35ax23bXNFbx5GNzDQ/UbxIvcH/lO41ScKNtc4PCbL5VUS6EkvjbwHsTr\nq9e6y49YvEiPqsFX2Bpr1AniVYXQW/sUaq1O0d8ollEcA/xO1HRfP1frduMD\nOMSFcmV+EuDcGPevz8smCEk67g0QD9zFnzuszjPD7wX/62pUanoUs7IbsoQp\nl8umQV786UsiSe4f2ucaGtO1fFQKgfPGSjTi6VRyCL8Eu6iU9qN6+JTUDlp2\nkE0/L9yCQt170THzbSKSsZd0ixs77cdH9683oZFwsFVHom6ENYrogkBQmZHi\njU1eVKeEpXCK+XaivgQfQlqd6HdK6L1rT+vhqRq72BJdG/FSRkWBqCdCTrkO\n7Q0BbDc15GXlzgAAvpsgIkjvZmDk/sL6tVXjlfDaJKBgDBWN+Eq+dq22a5BH\ncvzd\r\n=nVXP\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCICF37Zxb8ZxuOrTPviSFhYwhf/NAvmv0wTs/fCBjNJl+AiBKTHjXlj8LV3hgrkwbpdiR1psDcbfo0ZX/9PVDVpj8dg=="}]},"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"maintainers":[{"name":"leecade","email":"leecade@163.com"},{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_4.1.0-rc.0_1625493352331_0.48010999127823695"},"_hasShrinkwrap":false},"4.1.0-rc.1":{"name":"react-model","version":"4.1.0-rc.1","description":"The State management library for React","main":"./dist/react-model.js","module":"./dist/react-model.esm.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build:prod":"microbundle --define process.env.NODE_ENV=production --sourcemap false --jsx React.createElement --output dist --tsconfig ./tsconfig.json","build:dev":"microbundle --define process.env.NODE_ENV=development --sourcemap true --jsx React.createElement --output dist --tsconfig ./tsconfig.json","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":">=8.0.1"},"peerDependencies":{"react":">=16.3.0","react-dom":">=16.3.0","typescript":">=3.9.0"},"devDependencies":{"@commitlint/cli":"^8.1.0","@commitlint/config-conventional":"^8.1.0","@testing-library/react":"^10.0.1","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/faker":"^5.5.3","@types/jest":"^25.1.0","@types/node":"^14.0.0","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","faker":"^5.5.3","husky":"^4.0.2","jest":"^24.1.0","microbundle":"^0.12.3","prettier":"^2.0.0","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^8.0.0","remark-lint":"^7.0.0","remark-preset-lint-recommended":"^4.0.0","ts-jest":"^26.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"browserslist":["edge 17","firefox 70","chrome 48","safari 12.1","android 4.0","samsung 9.2"],"readme":"# react-model &middot; ![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) [![Build Status](https://travis-ci.org/byte-fe/react-model.svg?branch=master)](https://travis-ci.org/byte-fe/react-model) [![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 = () => {\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(Todo)\n\nconst App = () => {\n  return <TodoList />\n}\n\nconst TodoList = () => {\n  const { items, setItems } = useStore()\n  return <div>\n    <Addon handler={setItems} />\n    {state.items.map((item, index) => (<Todo key={index} item={item} />))}\n  </div>\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[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  - [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 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### 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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (payload, { state }) => {\n      return {\n        counter: state.counter + (payload || 1)\n      }\n    },\n    openLight: async (_, { state, actions }) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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<typeof Model.actions>\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 () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button onClick={e => actions.increment(33)}>home increment</button>\n      <button onClick={e => sharedActions.increment(20)}>\n        shared increment\n      </button>\n      <button onClick={e => actions.get()}>fake request</button>\n      <button onClick={e => actions.openLight()}>fake nested call</button>\n    </div>\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 >= 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> Hint: The state returned should only be used as readonly\n\n```jsx\nimport { useStore, getState } from '../model/index'\n\nconst BasicHook = () => {\n  const [state, actions] = useStore('Counter')\n  useEffect(() => {\n    console.log('some mounted actions from BasicHooks')\n    return () =>\n      console.log(\n        `Basic Hooks unmounted, current Counter state: ${JSON.stringify(\n          getState('Counter')\n        )}`\n      )\n  }, [])\n  return (\n    <>\n      <div>state: {JSON.stringify(state)}</div>\n    </>\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: () => {\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 = () => {\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (params, { state: s }) => {\n      // return (state: typeof s) => { // TypeScript < 3.9\n      return state => {\n        state.counter += params || 1\n      }\n    },\n    decrease: params => s => {\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) => {\n      return state => {\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<details>\n<summary>Store: shared.ts</summary>\n<p>\n\n```ts\nconst initialState = {\n  counter: 0\n}\n\nconst model: ModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: (params, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    }\n  },\n  // Provide for SSR\n  asyncState: async context => {\n    await waitFor(4000)\n    return { counter: 500 }\n  },\n  state: initialState\n}\n\nexport default model\n```\n\n</p>\n</details>\n\n<details>\n<summary>Global Config: _app.tsx</summary>\n<p>\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) => {\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    <Container>\n      <Component {...pageProps} />\n    </Container>\n  )\n}\n\nMyApp.getInitialProps = async (context: NextAppContext) => {\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</p>\n</details>\n\n<details>\n<summary>Page: hooks/index.tsx</summary>\n<p>\n\n```tsx\nimport { useStore, getState } from '../index'\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button\n        onClick={e => {\n          actions.increment(33)\n        }}\n      >\n    </div>\n  )\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Single Page Config: benchmark.tsx</summary>\n<p>\n\n```tsx\n// ...\nBenchmark.getInitialProps = async () => {\n  return await getInitialState({ modelName: 'Todo' }, { isServer: true })\n}\n```\n</p>\n</details>\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<{}> = async (context, restMiddlewares) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: any) => 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) => async (params: any) => {\n  const context: Context = {\n    modelName,\n    setState,\n    actionName: action.name,\n    next: () => {},\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<\n  { name: string }, // State Type\n  { ext: undefined }, // ActionParamsType\n  { name: string } // ExtContextType\n> = {\n  actions: {\n    // { state, action } => { state, action, [name] }\n    ext: (_, { name }) => {\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 => '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      <Provider>\n        <Counter />\n      </Provider>\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<details>\n<summary>Javascript decorator version</summary>\n<p>\n\n```jsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\n\nconst mapProps = ({ light, counter }) => ({\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      <>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(5)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n      </>\n    )\n  }\n}\n```\n\n</p>\n</details>\n\n<details>\n<summary>TypeScript Version</summary>\n<p>\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) => ({\n  lightStatus: light ? 'open' : 'close',\n  counter,\n  response\n})\n\ntype RType = ReturnType<typeof mapProps>\n\nclass TSCounter extends PureComponent<\n  { state: RType } & { actions: ActionType }\n> {\n  render() {\n    const { state, actions } = this.props\n    return (\n      <>\n        <div>TS Counter</div>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(3)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n        <button onClick={e => actions.get()}>Get Response</button>\n        <div>message: {JSON.stringify(state.response)}</div>\n      </>\n    )\n  }\n}\n\nexport default connect(\n  'Home',\n  mapProps\n)(TSCounter)\n```\n</p>\n</details>\n\n[⇧ back to top](#table-of-contents)\n\n## FAQ\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<State, ActionParams> = {\n  state: {\n    counter: 1\n    enable: false\n  },\n  actions: {\n    add: (payload) => state => {\n      state.counter += payload\n    },\n    switch: () => state => {\n      state.enable = !state.enable\n    }\n  }\n}\n```\n\n```ts\nconst Component = () => {\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 => 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 }) => ['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) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: Error) => 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) => {\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      <div className=\"App\">\n        <Counter />\n        <Counter />\n        <Counter />\n      </div>\n    )\n  }\n}\n```\n\n<details>\n<summary>Counter model</summary>\n<p>\n\n```ts\ninterface State {\n  count: number\n}\n\ninterface ActionParams {\n  increment: number\n}\n\nconst model: ModelType<State, ActionParams> = {\n  state: {\n    count: 0\n  },\n  actions: {\n    increment: payload => {\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 => (state.count += payload)\n      return state => {\n        state.count += payload\n      }\n    }\n  }\n}\n\n```\n\n</p>\n</details>\n\n<details>\n<summary>Counter.tsx</summary>\n<p>\n\n```tsx\n\nconst Counter = () => {\n  const [{ useStore }] = useState(() => Model(model))\n  const [state, actions] = useStore()\n  return (\n    <div>\n      <div>{state.count}</div>\n      <button onClick={() => actions.increment(3)}>Increment</button>\n    </div>\n  )\n}\n\nexport default Counter\n```\n\n</p>\n</details>\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<ExpensiveState, ExpensiveActionParams> = {\n  state: {\n    moduleList: []\n  },\n  actions: {\n    setPreFreezedDataset: () => {\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 => {\n    // Not allowed\n    // return state => (state.count += payload)\n    return state => {\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) => {\n  await timeout(1000, {})\n  context.next(restMiddlewares)\n}\n\nconst nextCounterModel: ModelType<CounterState, NextCounterActionParams> = {\n  actions: {\n    add: num => {\n      return state => {\n        state.count += num\n      }\n    },\n    increment: async (num, { actions }) => {\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","readmeFilename":"README.md","gitHead":"2559925a5f00925631b79b0cd9c9ebde8b9a5534","_id":"react-model@4.1.0-rc.1","_nodeVersion":"14.8.0","_npmVersion":"6.14.7","dist":{"integrity":"sha512-6DK+bdjrEMbxnWHbo1Vew7lZUhnAr4ryQzJMTGbPFWyNUoThTDrnsA+PcJtTtIo/z92Hy4Vv3Ea+bI9HhUcweg==","shasum":"c2fd9fe18eca23d1cdc5e9a4ac5f3ac5d154b9ed","tarball":"https://registry.npmjs.org/react-model/-/react-model-4.1.0-rc.1.tgz","fileCount":22,"unpackedSize":105397,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhASa5CRA9TVsSAnZWagAAukwP/AgDc3/2e1LfL3lYccx6\nVHhoQZ3VHTQ3egeoKr+s8aViP4aQDQqekrQDr0SYfe3jkXOXxjimbwgl9jOK\n8Vag7yWGBHMYsoDKa1O9ge/mR8Udnujw+au6HwsvvHFE9Ayt3PaeJgLrpHxW\nwuVeTvtUFBXtahXDZ0ZFtUvjLBbaZrSzkLsrhpaLqjlC/2xe6oy/i5vVkNZ8\n4HWxLvwtFekrwyUXYxPW0QpPOMIIfaV/QTD5uUJl595EbVawiWGzBTYTUJIn\nButOOeXOIwG5QqYwKZXJbZPsnbt07tcSmEUFeGH882crQFrqny0Y8QaXeeMf\noR9ggjzZADcXbXo9ZPBCKfocpyAplAkzMi4JOLgG6XgJeOvZCxKoqAuPtT94\nNhztjkpQXCgT1vISZbNsPO5tpO8CKO21ibbJaxb650TF2H0wIfQjabSmYDf7\nwOeUsNFwWqE9LCQGfiZOKzDA2u3BYelK3/JYITsU+OIXFGLddmXFP3114TGQ\nKBbJ0CpRc5UXaP3STpJ84d1unzVCfjuqHoCHLXynOjsql7Fh3w6SztV1zGAQ\nP9n7U9fnjxgyUoMX9g/K8GX07u6zye9L0Wf5wew9bwSOgu+CS817mZj/szql\nSKV4eWvFouKmQmCzMAiuT4WAxwjTpYOO3xzJfvk8+AWwOY/1tkqJYk3E+z8T\nT39h\r\n=BgyS\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCID8O7sYGi1bq/4JkIH1gtLuPVK/FJnhlWPfrIh8vs9t/AiAmW6MgO+sStjKLIXDGdpWl0bbkus9T2vl5knyJVdn43w=="}]},"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"maintainers":[{"name":"leecade","email":"leecade@163.com"},{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_4.1.0-rc.1_1627465401325_0.6265063969517295"},"_hasShrinkwrap":false},"4.1.0":{"name":"react-model","version":"4.1.0","description":"The State management library for React","main":"./dist/react-model.js","module":"./dist/react-model.esm.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build:prod":"microbundle --define process.env.NODE_ENV=production --sourcemap false --jsx React.createElement --output dist --tsconfig ./tsconfig.json","build:dev":"microbundle --define process.env.NODE_ENV=development --sourcemap true --jsx React.createElement --output dist --tsconfig ./tsconfig.json","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":">=8.0.1"},"peerDependencies":{"react":">=16.3.0","react-dom":">=16.3.0","typescript":">=3.9.0"},"devDependencies":{"@commitlint/cli":"^8.1.0","@commitlint/config-conventional":"^8.1.0","@testing-library/react":"^10.0.1","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/faker":"^5.5.3","@types/jest":"^25.1.0","@types/node":"^14.0.0","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","faker":"^5.5.3","husky":"^4.0.2","jest":"^24.1.0","microbundle":"^0.12.3","prettier":"^2.0.0","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^8.0.0","remark-lint":"^7.0.0","remark-preset-lint-recommended":"^4.0.0","ts-jest":"^26.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"browserslist":["edge 17","firefox 70","chrome 48","safari 12.1","android 4.0","samsung 9.2"],"gitHead":"2559925a5f00925631b79b0cd9c9ebde8b9a5534","_id":"react-model@4.1.0","_nodeVersion":"14.8.0","_npmVersion":"6.14.7","dist":{"integrity":"sha512-702sypGmVMjNvWkkjC2PcQQIGeAej2DIXottdMIn1sk+SY159v7+wZO9L/sDwWdrQT3pbtWsdrhq1Bup0qKYLQ==","shasum":"b00c20b4347b313a9791bf154b8324fae29b84e1","tarball":"https://registry.npmjs.org/react-model/-/react-model-4.1.0.tgz","fileCount":22,"unpackedSize":105392,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhCTIKCRA9TVsSAnZWagAACqcP/ittiN0GyKGrOqDCeR1X\n0K70zM3Wltip8XJDa8vQgTf0oXANxKOSPvQVVjrRmJ3UoaR7fgb9vu9b5ztz\nyyR/uqH0yGNombFO/StWCYEN6C9rJ/J8JfcswK0jertWfJAKJINz7Sc2PEFy\nmZhD7Cfz0uKUFRMpLFVrUoIyxLlqFyxd9xguW6xtbDDY0th3fatTj1rYxJjG\nlsVWd9V4BJEnMGS9i9XRHxe/mg6yElX3ayIX3Q5Ewq24JY8j22rYovn+VXZ+\nMD1NstiWfFnYelZKSMvzxdGr+fyZi/iRLh/PiHsuwEA9shE62TZKVqjLtjel\n5YnthFsQO+SIDUuOz5vdZxLZSzgEBGj957orflphGOTQxAVTD8gwhKP9CCwq\nebGaDdsKxWut19AG+/Vbb0FfzRu4ilsS74TZaIARbQRe/iqLmQREtyX2OfzF\ns5KkrCocNRGgbDVoquAdL0COdQWID4eKtHA9FbfJlefsuCZ/8Y/m7lF9sUZ2\nLnNmodO9N4p4ChT8t0L9upA5cX5zE4qf4GwDSDsepZRBxY/tJW93l7RcmQq4\nLPUR5wri550WybbemIBS31GkDJOfOEQH/u8lDDTrByG6FcJF3QDumeTMf2rw\nGxbJixjIvsteSH9D0ZlkvJXyxxPfazMoYN8+maGnetysrMRo88PJl7Lhn796\n4kto\r\n=rr8+\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDj/fEVEGGfsumEuOlsq4bP/MCT/Q9eWuOjoiNULHrCgwIhAIKlBz4J740q1IoSlVSCC1wz19riP+fh4LM/+6Sk1by9"}]},"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"maintainers":[{"name":"leecade","email":"leecade@163.com"},{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_4.1.0_1627992586138_0.6152185980754743"},"_hasShrinkwrap":false},"4.1.1-alpha.0":{"name":"react-model","version":"4.1.1-alpha.0","description":"The State management library for React","main":"./dist/react-model.js","module":"./dist/react-model.esm.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build:prod":"microbundle --define process.env.NODE_ENV=production --sourcemap false --jsx React.createElement --output dist --tsconfig ./tsconfig.json","build:dev":"microbundle --define process.env.NODE_ENV=development --sourcemap true --jsx React.createElement --output dist --tsconfig ./tsconfig.json","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":">=8.0.1"},"peerDependencies":{"react":">=16.3.0","react-dom":">=16.3.0","typescript":">=3.9.0"},"devDependencies":{"@commitlint/cli":"^8.1.0","@commitlint/config-conventional":"^8.1.0","@testing-library/react":"^10.0.1","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/faker":"^5.5.3","@types/jest":"^25.1.0","@types/node":"^14.0.0","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","faker":"^5.5.3","husky":"^4.0.2","jest":"^24.1.0","microbundle":"^0.12.3","prettier":"^2.0.0","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^8.0.0","remark-lint":"^7.0.0","remark-preset-lint-recommended":"^4.0.0","ts-jest":"^26.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"browserslist":["edge 17","firefox 70","chrome 48","safari 12.1","android 4.0","samsung 9.2"],"readme":"# react-model &middot; ![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) [![Build Status](https://travis-ci.org/byte-fe/react-model.svg?branch=master)](https://travis-ci.org/byte-fe/react-model) [![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 = () => {\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(Todo)\n\nconst App = () => {\n  return <TodoList />\n}\n\nconst TodoList = () => {\n  const { items, setItems } = useStore()\n  return <div>\n    <Addon handler={setItems} />\n    {state.items.map((item, index) => (<Todo key={index} item={item} />))}\n  </div>\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(() => {\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 = () => {\n    setLocalCount(localCount + 1)\n  }\n  const inc = () => {\n    setCount(c => 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 = () => {\n  const { count, localCount, inc, incLocal } = useStore()\n  return <div>\n    <span>count: { count }</span>\n    <span>localCount: { localCount }</span>\n    <button onClick={inc}>inc</button>\n    <button onClick={incLocal}>incLocal</button>\n  </div>\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (payload, { state }) => {\n      return {\n        counter: state.counter + (payload || 1)\n      }\n    },\n    openLight: async (_, { state, actions }) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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<typeof Model.actions>\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 () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button onClick={e => actions.increment(33)}>home increment</button>\n      <button onClick={e => sharedActions.increment(20)}>\n        shared increment\n      </button>\n      <button onClick={e => actions.get()}>fake request</button>\n      <button onClick={e => actions.openLight()}>fake nested call</button>\n    </div>\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 >= 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> Hint: The state returned should only be used as readonly\n\n```jsx\nimport { useStore, getState } from '../model/index'\n\nconst BasicHook = () => {\n  const [state, actions] = useStore('Counter')\n  useEffect(() => {\n    console.log('some mounted actions from BasicHooks')\n    return () =>\n      console.log(\n        `Basic Hooks unmounted, current Counter state: ${JSON.stringify(\n          getState('Counter')\n        )}`\n      )\n  }, [])\n  return (\n    <>\n      <div>state: {JSON.stringify(state)}</div>\n    </>\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: () => {\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 = () => {\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (params, { state: s }) => {\n      // return (state: typeof s) => { // TypeScript < 3.9\n      return state => {\n        state.counter += params || 1\n      }\n    },\n    decrease: params => s => {\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) => {\n      return state => {\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<details>\n<summary>Store: shared.ts</summary>\n<p>\n\n```ts\nconst initialState = {\n  counter: 0\n}\n\nconst model: ModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: (params, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    }\n  },\n  // Provide for SSR\n  asyncState: async context => {\n    await waitFor(4000)\n    return { counter: 500 }\n  },\n  state: initialState\n}\n\nexport default model\n```\n\n</p>\n</details>\n\n<details>\n<summary>Global Config: _app.tsx</summary>\n<p>\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) => {\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    <Container>\n      <Component {...pageProps} />\n    </Container>\n  )\n}\n\nMyApp.getInitialProps = async (context: NextAppContext) => {\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</p>\n</details>\n\n<details>\n<summary>Page: hooks/index.tsx</summary>\n<p>\n\n```tsx\nimport { useStore, getState } from '../index'\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button\n        onClick={e => {\n          actions.increment(33)\n        }}\n      >\n    </div>\n  )\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Single Page Config: benchmark.tsx</summary>\n<p>\n\n```tsx\n// ...\nBenchmark.getInitialProps = async () => {\n  return await getInitialState({ modelName: 'Todo' }, { isServer: true })\n}\n```\n</p>\n</details>\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<{}> = async (context, restMiddlewares) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: any) => 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) => async (params: any) => {\n  const context: Context = {\n    modelName,\n    setState,\n    actionName: action.name,\n    next: () => {},\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<\n  { name: string }, // State Type\n  { ext: undefined }, // ActionParamsType\n  { name: string } // ExtContextType\n> = {\n  actions: {\n    // { state, action } => { state, action, [name] }\n    ext: (_, { name }) => {\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 => '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      <Provider>\n        <Counter />\n      </Provider>\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<details>\n<summary>Javascript decorator version</summary>\n<p>\n\n```jsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\n\nconst mapProps = ({ light, counter }) => ({\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      <>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(5)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n      </>\n    )\n  }\n}\n```\n\n</p>\n</details>\n\n<details>\n<summary>TypeScript Version</summary>\n<p>\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) => ({\n  lightStatus: light ? 'open' : 'close',\n  counter,\n  response\n})\n\ntype RType = ReturnType<typeof mapProps>\n\nclass TSCounter extends PureComponent<\n  { state: RType } & { actions: ActionType }\n> {\n  render() {\n    const { state, actions } = this.props\n    return (\n      <>\n        <div>TS Counter</div>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(3)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n        <button onClick={e => actions.get()}>Get Response</button>\n        <div>message: {JSON.stringify(state.response)}</div>\n      </>\n    )\n  }\n}\n\nexport default connect(\n  'Home',\n  mapProps\n)(TSCounter)\n```\n</p>\n</details>\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<\n  CounterState,\n  CounterActionParams\n> = {\n  actions: {\n    increment: (params) => {\n      return (state) => {\n        state.count += params\n      }\n    }\n  },\n  state: { count: 0 }\n}\n\n// v4.1.x\nconst Counter = createStore(() => {\n  const [state, setState] = useModel({ count: 0 })\n  const actions = {\n    increment: (params) => {\n      setState((state) => {\n        state.count += params\n      })\n    }\n  }\n  return [state, actions]\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 = () => {\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<State, ActionParams> = {\n  state: {\n    counter: 1\n    enable: false\n  },\n  actions: {\n    add: (payload) => state => {\n      state.counter += payload\n    },\n    switch: () => state => {\n      state.enable = !state.enable\n    }\n  }\n}\n```\n\n```ts\nconst Component = () => {\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 => 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 }) => ['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) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: Error) => 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) => {\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      <div className=\"App\">\n        <Counter />\n        <Counter />\n        <Counter />\n      </div>\n    )\n  }\n}\n```\n\n<details>\n<summary>Counter model</summary>\n<p>\n\n```ts\ninterface State {\n  count: number\n}\n\ninterface ActionParams {\n  increment: number\n}\n\nconst model: ModelType<State, ActionParams> = {\n  state: {\n    count: 0\n  },\n  actions: {\n    increment: payload => {\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 => (state.count += payload)\n      return state => {\n        state.count += payload\n      }\n    }\n  }\n}\n\n```\n\n</p>\n</details>\n\n<details>\n<summary>Counter.tsx</summary>\n<p>\n\n```tsx\n\nconst Counter = () => {\n  const [{ useStore }] = useState(() => Model(model))\n  const [state, actions] = useStore()\n  return (\n    <div>\n      <div>{state.count}</div>\n      <button onClick={() => actions.increment(3)}>Increment</button>\n    </div>\n  )\n}\n\nexport default Counter\n```\n\n</p>\n</details>\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<ExpensiveState, ExpensiveActionParams> = {\n  state: {\n    moduleList: []\n  },\n  actions: {\n    setPreFreezedDataset: () => {\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 => {\n    // Not allowed\n    // return state => (state.count += payload)\n    return state => {\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) => {\n  await timeout(1000, {})\n  context.next(restMiddlewares)\n}\n\nconst nextCounterModel: ModelType<CounterState, NextCounterActionParams> = {\n  actions: {\n    add: num => {\n      return state => {\n        state.count += num\n      }\n    },\n    increment: async (num, { actions }) => {\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","readmeFilename":"README.md","gitHead":"99d6fbf921593aae83f38244b032a5dad0355b20","_id":"react-model@4.1.1-alpha.0","_nodeVersion":"14.8.0","_npmVersion":"6.14.7","dist":{"integrity":"sha512-mMRCSzNF2t+7JrMhQWvikznnxj4zm/oqU1ZSxb/IfEShTwdPolcBBHFw+ti7mECMDhwbnJP4G4IVovIdErBS7w==","shasum":"c2fa3549251ddb8f8648d2b241fa41c95b47f91d","tarball":"https://registry.npmjs.org/react-model/-/react-model-4.1.1-alpha.0.tgz","fileCount":22,"unpackedSize":108531,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhCoYbCRA9TVsSAnZWagAAKvgP/0/l2zmBW6sUlwccdcxN\ndDqKBCxBWQtrAbzG20lUoE7XoFS9Z99Sye+GbvSghAZT0/pkL/t5u4WodqU2\nUYWTdjmck9kuK2IgKgeTTOcKDxKIoEeG3uH9UZLIRJASSmrQli0J7l+jRTb+\n3CmOTA28utNPq9B/Ho+nmnCb5cgJfJJ9S392E2ZzoPwGvuR/SQjBmfcQ9sbe\nJ9yagrbodc6JnCec3I+MN9h96GHgcUEH2U4sjbkOx9n7KXIQ4xEBwXc1iX37\nQ1G8RKjeTEdpn2rPzWJ8qWq4TaPSiLjdKz7rTVkHCiDbqrO/SuCEknYEVKbs\nsR+fmyYx9i2A16+uU9MxeM2mOS8mNbIaYw9XFd1F2i4iA5kI3SxTda+6pW0d\naskAVHOEQmNW8rxBZk5FLUp+DfkWRxgslYgyf6L2v1uzLgsD9Z0bqMzD7MgU\nVldGHwekE+8grkuQbmIz1xd88pmhQxnv29fvULmpbGx3XyDOcNS+qrVr3p/I\nh5Ar1rrJ/ewyQLg80MLUZrdg9f/48d5Ns5pNKPzkl2Pa2Dk+Bv1yyFixO77l\nvXEPUpBRh+RRk3a+3j7I38FnycoLfmoDv+AE34LyYSV7+dFHgOtzzj1B0IMf\nY/SD8OuLgXBh4fKNdzyrA7H5YbbIgM5RMwHfYjrI4z2I67INziYnVCriGxZd\n8Pj/\r\n=2HR4\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCeneW8FTB9+8wAH5Vpj7NaW3RLZa+tlkhoBmQE9j8M1gIgTtXs9/6qP62m/oLgyroMZEU9BYYZyvEgrBS5drjIuAU="}]},"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"maintainers":[{"name":"leecade","email":"leecade@163.com"},{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_4.1.1-alpha.0_1628079643596_0.3787804324326136"},"_hasShrinkwrap":false},"4.1.1":{"name":"react-model","version":"4.1.1","description":"The State management library for React","main":"./dist/react-model.js","module":"./dist/react-model.esm.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build:prod":"microbundle --define process.env.NODE_ENV=production --sourcemap false --jsx React.createElement --output dist --tsconfig ./tsconfig.json","build:dev":"microbundle --define process.env.NODE_ENV=development --sourcemap true --jsx React.createElement --output dist --tsconfig ./tsconfig.json","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":">=8.0.1"},"peerDependencies":{"react":">=16.3.0","react-dom":">=16.3.0","typescript":">=3.9.0"},"devDependencies":{"@commitlint/cli":"^8.1.0","@commitlint/config-conventional":"^8.1.0","@testing-library/react":"^10.0.1","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/faker":"^5.5.3","@types/jest":"^25.1.0","@types/node":"^14.0.0","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","faker":"^5.5.3","husky":"^4.0.2","jest":"^24.1.0","microbundle":"^0.12.3","prettier":"^2.0.0","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^8.0.0","remark-lint":"^7.0.0","remark-preset-lint-recommended":"^4.0.0","ts-jest":"^26.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"browserslist":["edge 17","firefox 70","chrome 48","safari 12.1","android 4.0","samsung 9.2"],"gitHead":"4424c1c1a34c369e8f24957d0b561888607c0083","_id":"react-model@4.1.1","_nodeVersion":"14.8.0","_npmVersion":"6.14.7","dist":{"integrity":"sha512-eu/22HZ3mqxFCgAUmp9l33LMIJC/vVomzRc7wcbdXlLE5fHJ/0BjynmGtK1i8JyUd/1LkbRTbfE3SuTWO6e3GA==","shasum":"6235d4339ca133b9246c867a1db24b819e23ac14","tarball":"https://registry.npmjs.org/react-model/-/react-model-4.1.1.tgz","fileCount":22,"unpackedSize":108532,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhEN6NCRA9TVsSAnZWagAAVOUQAIoKc7c4i90kwDQqCAOi\nNfIOUZVXc4+4ePp2kdyNiFM9vYq7d3DhpIvLIXUcjvPCdfNSw+jnkwSOlr4H\nRgqMVgJ/x+dGN1ZBQpNajfkwftEF2ShrdXSopHCsKhAr9moHrqAEkkmC9yu8\nTBsD1jf1jx6Lr+ONQnaJsTbGpBJRlUFtUVEjJZqVmI2igNX5yrGKoqxVpdpo\ns8E/PtolKlhyvEng3yTCRaGP3iHroC2wFOdplgJgsafoW1soyZS5DchGgfPx\nJ1imdgbzMe+z6ERKeTOFHivl5G/FYbggUm4sMRgeBYwQvuApmhUijDM4JEgA\n6k/rcFG6y65o/+sOufCxsf+OKNfGKQVNfsUzuRsKT3OY8g3D7uVQqW+VwvoV\n8rNsZKJNH7kw3jCEO6u687iOXD0w1l/d4rmjAQTcMvNYRHRk+Ai/h6Tlmbbt\nu2n34+U8G6LalP8/6HfF+JF61aytGN2xnFYivr+EGPSkqWp8rarvSgBNIaLz\n6XltXlMJhpkwKh/PFLPZ3pPj3sdp4FGXKgkhX3yQCwU9gCql9VJSZE1S3ppg\nEtN18v5MQgz4NNnxv7gNF28wHF1nj5EgZNReL7HkZY+4YZuoTwgIXmX0lRTw\n5C7R2NwNeHViY4tyW7F/HRsEfpV8wER8yRiitAV2kI6xh15/W55k7QyIQf/+\n4Qm0\r\n=RxGY\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIDkU5yTIvqKl0QpFIzg95Xistd1l1uqYu/s2G6acogDWAiBaJDflxLgIFcIkL81SvHDvY7Wn1ceDWU4vOag9FUvWbQ=="}]},"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"maintainers":[{"name":"leecade","email":"leecade@163.com"},{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_4.1.1_1628495501606_0.516503150369936"},"_hasShrinkwrap":false},"4.1.2-rc.0":{"name":"react-model","version":"4.1.2-rc.0","description":"The State management library for React","main":"./dist/react-model.js","module":"./dist/react-model.esm.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build:prod":"microbundle --define process.env.NODE_ENV=production --sourcemap false --jsx React.createElement --output dist --tsconfig ./tsconfig.json","build:dev":"microbundle --define process.env.NODE_ENV=development --sourcemap true --jsx React.createElement --output dist --tsconfig ./tsconfig.json","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":">=8.0.1"},"peerDependencies":{"react":">=16.3.0","react-dom":">=16.3.0","typescript":">=3.9.0"},"devDependencies":{"@commitlint/cli":"^8.1.0","@commitlint/config-conventional":"^8.1.0","@testing-library/react":"^10.0.1","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/faker":"^5.5.3","@types/jest":"^25.1.0","@types/node":"^14.0.0","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","faker":"^5.5.3","husky":"^4.0.2","jest":"^24.1.0","microbundle":"^0.12.3","prettier":"^2.0.0","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^8.0.0","remark-lint":"^7.0.0","remark-preset-lint-recommended":"^4.0.0","ts-jest":"^26.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"browserslist":["edge 17","firefox 70","chrome 48","safari 12.1","android 4.0","samsung 9.2"],"readme":"# react-model &middot; ![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) [![Build Status](https://travis-ci.org/byte-fe/react-model.svg?branch=master)](https://travis-ci.org/byte-fe/react-model) [![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 = () => {\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(Todo)\n\nconst App = () => {\n  return <TodoList />\n}\n\nconst TodoList = () => {\n  const { items, setItems } = useStore()\n  return <div>\n    <Addon handler={setItems} />\n    {state.items.map((item, index) => (<Todo key={index} item={item} />))}\n  </div>\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(() => {\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 = () => {\n    setLocalCount(localCount + 1)\n  }\n  const inc = () => {\n    setCount(c => 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 = () => {\n  const { count, localCount, inc, incLocal } = useStore()\n  return <div>\n    <span>count: { count }</span>\n    <span>localCount: { localCount }</span>\n    <button onClick={inc}>inc</button>\n    <button onClick={incLocal}>incLocal</button>\n  </div>\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (payload, { state }) => {\n      return {\n        counter: state.counter + (payload || 1)\n      }\n    },\n    openLight: async (_, { state, actions }) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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<typeof Model.actions>\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 () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button onClick={e => actions.increment(33)}>home increment</button>\n      <button onClick={e => sharedActions.increment(20)}>\n        shared increment\n      </button>\n      <button onClick={e => actions.get()}>fake request</button>\n      <button onClick={e => actions.openLight()}>fake nested call</button>\n    </div>\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 >= 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> Hint: The state returned should only be used as readonly\n\n```jsx\nimport { useStore, getState } from '../model/index'\n\nconst BasicHook = () => {\n  const [state, actions] = useStore('Counter')\n  useEffect(() => {\n    console.log('some mounted actions from BasicHooks')\n    return () =>\n      console.log(\n        `Basic Hooks unmounted, current Counter state: ${JSON.stringify(\n          getState('Counter')\n        )}`\n      )\n  }, [])\n  return (\n    <>\n      <div>state: {JSON.stringify(state)}</div>\n    </>\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: () => {\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 = () => {\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (params, { state: s }) => {\n      // return (state: typeof s) => { // TypeScript < 3.9\n      return state => {\n        state.counter += params || 1\n      }\n    },\n    decrease: params => s => {\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) => {\n      return state => {\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<details>\n<summary>Store: shared.ts</summary>\n<p>\n\n```ts\nconst initialState = {\n  counter: 0\n}\n\nconst model: ModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: (params, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    }\n  },\n  // Provide for SSR\n  asyncState: async context => {\n    await waitFor(4000)\n    return { counter: 500 }\n  },\n  state: initialState\n}\n\nexport default model\n```\n\n</p>\n</details>\n\n<details>\n<summary>Global Config: _app.tsx</summary>\n<p>\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) => {\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    <Container>\n      <Component {...pageProps} />\n    </Container>\n  )\n}\n\nMyApp.getInitialProps = async (context: NextAppContext) => {\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</p>\n</details>\n\n<details>\n<summary>Page: hooks/index.tsx</summary>\n<p>\n\n```tsx\nimport { useStore, getState } from '../index'\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button\n        onClick={e => {\n          actions.increment(33)\n        }}\n      >\n    </div>\n  )\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Single Page Config: benchmark.tsx</summary>\n<p>\n\n```tsx\n// ...\nBenchmark.getInitialProps = async () => {\n  return await getInitialState({ modelName: 'Todo' }, { isServer: true })\n}\n```\n</p>\n</details>\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<{}> = async (context, restMiddlewares) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: any) => 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) => async (params: any) => {\n  const context: Context = {\n    modelName,\n    setState,\n    actionName: action.name,\n    next: () => {},\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<\n  { name: string }, // State Type\n  { ext: undefined }, // ActionParamsType\n  { name: string } // ExtContextType\n> = {\n  actions: {\n    // { state, action } => { state, action, [name] }\n    ext: (_, { name }) => {\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 => '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      <Provider>\n        <Counter />\n      </Provider>\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<details>\n<summary>Javascript decorator version</summary>\n<p>\n\n```jsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\n\nconst mapProps = ({ light, counter }) => ({\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      <>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(5)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n      </>\n    )\n  }\n}\n```\n\n</p>\n</details>\n\n<details>\n<summary>TypeScript Version</summary>\n<p>\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) => ({\n  lightStatus: light ? 'open' : 'close',\n  counter,\n  response\n})\n\ntype RType = ReturnType<typeof mapProps>\n\nclass TSCounter extends PureComponent<\n  { state: RType } & { actions: ActionType }\n> {\n  render() {\n    const { state, actions } = this.props\n    return (\n      <>\n        <div>TS Counter</div>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(3)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n        <button onClick={e => actions.get()}>Get Response</button>\n        <div>message: {JSON.stringify(state.response)}</div>\n      </>\n    )\n  }\n}\n\nexport default connect(\n  'Home',\n  mapProps\n)(TSCounter)\n```\n</p>\n</details>\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<\n  CounterState,\n  CounterActionParams\n> = {\n  actions: {\n    increment: (params) => {\n      return (state) => {\n        state.count += params\n      }\n    }\n  },\n  state: { count: 0 }\n}\n\n// v4.1.x\nconst Counter = createStore(() => {\n  const [state, setState] = useModel({ count: 0 })\n  const actions = {\n    increment: (params) => {\n      setState((state) => {\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 = () => {\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<State, ActionParams> = {\n  state: {\n    counter: 1\n    enable: false\n  },\n  actions: {\n    add: (payload) => state => {\n      state.counter += payload\n    },\n    switch: () => state => {\n      state.enable = !state.enable\n    }\n  }\n}\n```\n\n```ts\nconst Component = () => {\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 => 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 }) => ['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) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: Error) => 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) => {\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      <div className=\"App\">\n        <Counter />\n        <Counter />\n        <Counter />\n      </div>\n    )\n  }\n}\n```\n\n<details>\n<summary>Counter model</summary>\n<p>\n\n```ts\ninterface State {\n  count: number\n}\n\ninterface ActionParams {\n  increment: number\n}\n\nconst model: ModelType<State, ActionParams> = {\n  state: {\n    count: 0\n  },\n  actions: {\n    increment: payload => {\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 => (state.count += payload)\n      return state => {\n        state.count += payload\n      }\n    }\n  }\n}\n\n```\n\n</p>\n</details>\n\n<details>\n<summary>Counter.tsx</summary>\n<p>\n\n```tsx\n\nconst Counter = () => {\n  const [{ useStore }] = useState(() => Model(model))\n  const [state, actions] = useStore()\n  return (\n    <div>\n      <div>{state.count}</div>\n      <button onClick={() => actions.increment(3)}>Increment</button>\n    </div>\n  )\n}\n\nexport default Counter\n```\n\n</p>\n</details>\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<ExpensiveState, ExpensiveActionParams> = {\n  state: {\n    moduleList: []\n  },\n  actions: {\n    setPreFreezedDataset: () => {\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 => {\n    // Not allowed\n    // return state => (state.count += payload)\n    return state => {\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) => {\n  await timeout(1000, {})\n  context.next(restMiddlewares)\n}\n\nconst nextCounterModel: ModelType<CounterState, NextCounterActionParams> = {\n  actions: {\n    add: num => {\n      return state => {\n        state.count += num\n      }\n    },\n    increment: async (num, { actions }) => {\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","readmeFilename":"README.md","gitHead":"07aa3e5e1a876b03094d1c5d4cd83ef7d8f5cfdd","_id":"react-model@4.1.2-rc.0","_nodeVersion":"14.8.0","_npmVersion":"6.14.7","dist":{"integrity":"sha512-G6xovV/8X28n4NgRrdbj37XWoQtVNATNlkTNkmbHZc5Xd/kVZMs/F4w0/g/Lk2foZSlnJTjT89kvfMwNU66n6A==","shasum":"568b62d9a21872b3e92aa6f8b7738ed03a252d07","tarball":"https://registry.npmjs.org/react-model/-/react-model-4.1.2-rc.0.tgz","fileCount":22,"unpackedSize":108809,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhNc+wCRA9TVsSAnZWagAAFowP/AvgE6prm9e9up6jdnnT\nqhqfTwI+qFDmpey7tQqImACBZSFcqkPsx/FUUFsQcQhuSfIMsv+gKtCFkelP\nadvLXjQ7RtSOzen6qOWrR/43pE9ZIKUMzQhQIndCMZOx57GVGVNd8Qqg77TR\noLrqDBZKj7PXt1eHqZN/t26JnhJd5rloa2F2IUE7DzftjOmazgJonL/7Aody\ng1dI7DZWZchm/uZUiQ6Dt+vfetE9sDaDarsXm+9iodpa4uizu4ARpEw0p5G7\nGHjic3Hx6D1j1LQ+86Nj0fz9iKARLfB4ZVh4TEyhod2SKJHpTLPZ54IsmWK/\nzY0c7YCQr1VnGGLHIapfthj8ghUcmgUB6qKUmvObIV+W+PGNjDMjDllYCSHN\naJciFaSA5cCyUnwH/ulk827JmskAZ7lBvjMrO6jlDiLwOWIjMVgXk7tZDAg5\nJe7R9+Jy9drTPk9noij0x4BPWw+AWvdT2FSgyDSt0NLQ23fiVCSuBPascOzB\nqXoaCuMBnYf8dtRe+6jGOywYSCvK0IFhGYwm+4mAfEMYxa1hehANvipWaaGg\nVM0R42DvqOTIv4nKLWr+0I25kN7c2x5nf64aaZFctmxbSlRzXelG/+sKW6H2\nTOJZcGkw49+bApyrmtLmmnUkEi8tbfgHkbvr18NnoVcMAJ+VDbcTZiOQmL24\n2ifg\r\n=dyqT\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIEa8CBh7LDKFpAYDK8tvl6c1KQqtXV8YFChzVJI3FG1YAiEA24bhpOERWadFYL+ughYDH9VsXElwrp30PBNveeG/U4o="}]},"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"maintainers":[{"name":"leecade","email":"leecade@163.com"},{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_4.1.2-rc.0_1630916528152_0.8639054750894772"},"_hasShrinkwrap":false},"4.1.2":{"name":"react-model","version":"4.1.2","description":"The State management library for React","main":"./dist/react-model.js","module":"./dist/react-model.esm.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build:prod":"microbundle --define process.env.NODE_ENV=production --sourcemap false --jsx React.createElement --output dist --tsconfig ./tsconfig.json","build:dev":"microbundle --define process.env.NODE_ENV=development --sourcemap true --jsx React.createElement --output dist --tsconfig ./tsconfig.json","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":">=8.0.1"},"peerDependencies":{"react":">=16.3.0","react-dom":">=16.3.0","typescript":">=3.9.0"},"devDependencies":{"@commitlint/cli":"^8.1.0","@commitlint/config-conventional":"^8.1.0","@testing-library/react":"^10.0.1","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/faker":"^5.5.3","@types/jest":"^25.1.0","@types/node":"^14.0.0","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","faker":"^5.5.3","husky":"^4.0.2","jest":"^24.1.0","microbundle":"^0.12.3","prettier":"^2.0.0","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^8.0.0","remark-lint":"^7.0.0","remark-preset-lint-recommended":"^4.0.0","ts-jest":"^26.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"browserslist":["edge 17","firefox 70","chrome 48","safari 12.1","android 4.0","samsung 9.2"],"gitHead":"07aa3e5e1a876b03094d1c5d4cd83ef7d8f5cfdd","_id":"react-model@4.1.2","_nodeVersion":"14.8.0","_npmVersion":"6.14.7","dist":{"integrity":"sha512-SfFAdR8GFaRICOTBVKi5UL/1RxFggjmKMeN0OL9pyg6tw6eU4Km2Tk3ZTbAKxCo75D5FB3RvqbhtZDKyhHcLHw==","shasum":"7d25ccadff7714f97aa0f4a510eecbd205f8208f","tarball":"https://registry.npmjs.org/react-model/-/react-model-4.1.2.tgz","fileCount":22,"unpackedSize":108804,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhNdfKCRA9TVsSAnZWagAAekoP/iIy9eotBhqQP2xvonHK\nvUx36vI/0at4JVyonhdQMNtQsn51Tk7Hhl0+R1lr/Cze/uKKdM6si0SePfnx\narhDogbN84KaEqv6ehTkRXtBKKi3aBrW+1TMmoLuZLLgeHah65RH0f8fq0LK\n6bAbEg8rSwTSRMLGH2ejIP/1gFSkgjAkoOxgQcttE7puxKoRN4ZK6LZizGQa\nO7DZlQJVZGd5wuBrqbAFaynFg9apOgtrXqjfTVRdvnfIC6X3MU0O2L2R6MqP\nxORJpWw1ChGbNKTPG8ge0Mdem8TiR/M+0DlaDV7/VkppqgXHYIdMftxHIpPX\n4tXXkSfLERadnNUiwdUX6NBI6e0P3YPLT2bpDfe3QJwj2lKkp0GpxalnfVV4\nzdl5jjjLRXzwkYj55jC+cSCbfUWy1uO5HbBw+3DYaOdV5JMOGdlND7pH/4aZ\nF+o9/y33MsNdIQgzAJojnOTfDIpSN9tX9988+d7ECFJwepxjrcbxfK287T1Z\n42MipXvf8meBjchp1OVjpqUnXA7lLr94nh2o9Yjy0HkchaUnyOUe9wWP3ENf\n0tdMxkvFOH3kq3cWIMc+rMlbu9bobbCJXUO7X90wnbTfokyMBAXNtevkcsHp\ne2EmTDwRcQcHW7H1FEH7hn9wk6VkCJGr8jwnjQVIxq2W57FPnH/uC1THzZRY\nlfpV\r\n=cLMW\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIBff7DbgXo7yXTPiTP9j/4v2ScFflLNkpPiqSoTVBq68AiBqYRDlT+N6prLPmfEChTXV7I+dJixs06CiTaC9UiOzHA=="}]},"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"maintainers":[{"name":"leecade","email":"leecade@163.com"},{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_4.1.2_1630918602329_0.8135121344105902"},"_hasShrinkwrap":false},"4.1.3":{"name":"react-model","version":"4.1.3","description":"The State management library for React","main":"./dist/react-model.js","module":"./dist/react-model.esm.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build:prod":"microbundle --define process.env.NODE_ENV=production --sourcemap false --jsx React.createElement --output dist --tsconfig ./tsconfig.json","build:dev":"microbundle --define process.env.NODE_ENV=development --sourcemap true --jsx React.createElement --output dist --tsconfig ./tsconfig.json","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":">=8.0.1"},"peerDependencies":{"react":">=16.3.0","react-dom":">=16.3.0","typescript":">=3.9.0"},"devDependencies":{"@commitlint/cli":"^8.1.0","@commitlint/config-conventional":"^8.1.0","@testing-library/react":"^10.0.1","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/faker":"^5.5.3","@types/jest":"^25.1.0","@types/node":"^14.0.0","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","faker":"^5.5.3","husky":"^4.0.2","jest":"^24.1.0","microbundle":"^0.12.3","prettier":"^2.0.0","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^8.0.0","remark-lint":"^7.0.0","remark-preset-lint-recommended":"^4.0.0","ts-jest":"^26.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"browserslist":["edge 17","firefox 70","chrome 48","safari 12.1","android 4.0","samsung 9.2"],"gitHead":"6d4f5418c7285cb9e7886a75d4f023b23be8f2c0","_id":"react-model@4.1.3","_nodeVersion":"14.8.0","_npmVersion":"7.22.0","dist":{"integrity":"sha512-jy36U+YymZPxDwBiMxuYRKR2vxzF9jA3HjMOeZAAwkNXBWIctWQ2EddSnAEYwiTXskJgnxKuEtf5PdDd2tzNjg==","shasum":"def7e1b32db96ce85818d2087886e09d0fa93c64","tarball":"https://registry.npmjs.org/react-model/-/react-model-4.1.3.tgz","fileCount":22,"unpackedSize":110443,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhOhBwCRA9TVsSAnZWagAAjwsP/2mKNwO1bRufrPHbAlG/\nsj7yrrAbFjFO/+MiM/wg/gr0W5KOaAIa2fdKuyAfuAsGCETQrHwPDLS3FPcH\nmp9FimToD0AAGWsc3YgwHoOL3P/J4a4zHs+i+YumRvParerm8h05pHEUiJhG\njvUKauL28A9XisVOf3p6UIFWr4i9UP0igUFBOXQSygOrsO/qnWU04V5c2V9K\nQyFdjdrvEDaJoee/tdH4epQ5WX0erj7Ah9wOInG9kJ+Yzy9V5GVXDijBgPdG\nPL99QQ0pQuD691/yhenX1dP8k5uwp8q16Vd/4ZSs6pZuOFAkHG5kgCF5XccD\nDnP7WTCpDPoWmPsJH/l4n/NdVU1r710QG2A/0ABqwOG8ukjj+e4foiM65u9X\nJioXKcbQ9SvwsuoR+9v2GwrbHWH2+l2wxXtRH3/3LC4jRw5E789Fls5izSr+\nH4uAamw2EMG2qpaSRhww/2Z998nOP/NPOZvuRzwoqfkWIn88dDFwoUyo+AJJ\nQiUDTIVBQbIXwDWvjJMfXLjYwFbCKxM9j0v43GAdImCWNp+eVdLgpOtzln8m\nSul6QaWzeE/Gv7IVOSFiU0MBsm5Okz8m0tUt+niLbbGLZrQ26o1AO1LCthAm\nma2ebfPoz9XHUUVDLP8zAmhVPTME9N2xylgGp/o6MDPXqZ1uMhpXB8VozPnx\nhxPJ\r\n=TayV\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQC/MqhPIcuUoX+ObvJGLuAKObUBK81fGjTf7Y5ZlMtrxQIgVXqjTd0kfpoLK/I+bTPQC5QQNPYUHXsxHkGrVxq56Qg="}]},"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"maintainers":[{"name":"leecade","email":"leecade@163.com"},{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_4.1.3_1631195248663_0.5977673183426657"},"_hasShrinkwrap":false},"4.1.4":{"name":"react-model","version":"4.1.4","description":"The State management library for React","main":"./dist/react-model.js","module":"./dist/react-model.esm.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build:prod":"microbundle --define process.env.NODE_ENV=production --sourcemap false --jsx React.createElement --output dist --tsconfig ./tsconfig.json","build:dev":"microbundle --define process.env.NODE_ENV=development --sourcemap true --jsx React.createElement --output dist --tsconfig ./tsconfig.json","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":">=8.0.1"},"peerDependencies":{"react":">=16.3.0","react-dom":">=16.3.0","typescript":">=3.9.0"},"devDependencies":{"@commitlint/cli":"^8.1.0","@commitlint/config-conventional":"^8.1.0","@testing-library/react":"^10.0.1","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/faker":"^5.5.3","@types/jest":"^25.1.0","@types/node":"^14.0.0","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","faker":"^5.5.3","husky":"^4.0.2","jest":"^24.1.0","microbundle":"^0.12.3","prettier":"^2.0.0","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^8.0.0","remark-lint":"^7.0.0","remark-preset-lint-recommended":"^4.0.0","ts-jest":"^26.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"browserslist":["edge 17","firefox 70","chrome 48","safari 12.1","android 4.0","samsung 9.2"],"gitHead":"fb7cd26b75d26e5e7aabd160933ce52310129d14","_id":"react-model@4.1.4","_nodeVersion":"14.8.0","_npmVersion":"7.22.0","dist":{"integrity":"sha512-2FIhHtNXzvML8nNMXyunmoCXmYvBxj+MQCFM48XX32b3OcqUKWG9MAGVgB1YFl/r3tGh/xVYDpBrlSC3WkZqlw==","shasum":"4289567daf8ac3a8b7ae09f3b6c94f7919e45e0c","tarball":"https://registry.npmjs.org/react-model/-/react-model-4.1.4.tgz","fileCount":22,"unpackedSize":110454,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhOxKCCRA9TVsSAnZWagAAaKkQAJ5EzALHwamq8qJpqCt2\ntmrPLJuUNi5cdCbHFsM7KkR2IvyYxNyqtCxJaURBnSncCBOWclTmOB77DZuQ\nuy93Hu9ZfNRYVwCnB4jOMJEKOzCKyOsWnLh7GLSu8XZcXsv+MtUEDn3apc6/\n4FzNXRb5LMRjYr+yvacDMXB2QcbZeVBWYYctrdyjcAai7MDGWIqfwKxc/02Y\nZt1VSsqAWTIgsE8cmKJCJY5j56cnvqohXSaPmVfRnngWKterSoc8TJPej3E0\not4mbsgBmy0T1WHy08nnE/iaGvJNJjrh8uRJ009YesvDgV5p8BlnncuFVlUF\nFjTq59dXkmRRLFge16JAMd8GF6wiS5Q+gNnzgjXC0xNdShGPb83id/XWG3n6\nZVKQDraYZHcYOg5R8MsmopWGRZ3f6sikBmvJPQtqUvdoQeOr1buVBCuuMqR8\nW7wTPRt3y3sYMmcHdSyJzae/v2ryCPwsTBEA7a96c3BAHM17UiJQSv8qsJos\nsHs0n2ESbm/aJYHmndBpTBqbeuJXyh8TkGL1m8KoqUBRUBptLioEQsuCAwZf\nNRO6Vdr+KZlauXDRJO3h/NH+KfV2HNQLSCwlpb/XC/KgutF2VXLS8EKBdE/3\n0Jun4G+4Si7BDJZv2Tw1nrQBOVXIrVW1uD1ovV/QFEIXw16HDjMXfLFBVdzO\nYqbm\r\n=MlYF\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIBT8HdiOdT3lnxDNU1atSEwE9l11A77eA+a9Q9V+a2OuAiAYxdpw2dmx7stp9F9gLvhYtuo5NOkxokwBTgPrFuKORw=="}]},"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"maintainers":[{"name":"leecade","email":"leecade@163.com"},{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_4.1.4_1631261314085_0.2388652241672029"},"_hasShrinkwrap":false},"4.2.0-rc.0":{"name":"react-model","version":"4.2.0-rc.0","description":"The State management library for React","main":"./dist/react-model.js","module":"./dist/react-model.esm.js","umd:main":"./dist/react-model.umd.js","scripts":{"build:prod":"microbundle --define process.env.NODE_ENV=production --sourcemap false --jsx React.createElement --output dist --tsconfig ./tsconfig.json","build:dev":"microbundle --define process.env.NODE_ENV=development --sourcemap true --jsx React.createElement --output dist --tsconfig ./tsconfig.json","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":">=8.0.1"},"peerDependencies":{"react":">=16.3.0","react-dom":">=16.3.0","typescript":">=3.9.0"},"devDependencies":{"@commitlint/cli":"^8.1.0","@commitlint/config-conventional":"^8.1.0","@testing-library/react":"^10.0.1","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/faker":"^5.5.3","@types/jest":"^25.1.0","@types/node":"^14.0.0","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","faker":"^5.5.3","husky":"^4.0.2","jest":"^24.1.0","microbundle":"^0.12.3","prettier":"^2.0.0","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^8.0.0","remark-lint":"^7.0.0","remark-preset-lint-recommended":"^4.0.0","ts-jest":"^26.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"browserslist":["edge 17","firefox 70","chrome 48","safari 12.1","android 4.0","samsung 9.2"],"readme":"# react-model &middot; ![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) [![Build Status](https://travis-ci.org/byte-fe/react-model.svg?branch=master)](https://travis-ci.org/byte-fe/react-model) [![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 = () => {\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(Todo)\n\nconst App = () => {\n  return <TodoList />\n}\n\nconst TodoList = () => {\n  const { items, setItems } = useStore()\n  return <div>\n    <Addon handler={setItems} />\n    {state.items.map((item, index) => (<Todo key={index} item={item} />))}\n  </div>\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(() => {\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 = () => {\n    setLocalCount(localCount + 1)\n  }\n  const inc = () => {\n    setCount(c => 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 = () => {\n  const { count, localCount, inc, incLocal } = useStore()\n  return <div>\n    <span>count: { count }</span>\n    <span>localCount: { localCount }</span>\n    <button onClick={inc}>inc</button>\n    <button onClick={incLocal}>incLocal</button>\n  </div>\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (payload, { state }) => {\n      return {\n        counter: state.counter + (payload || 1)\n      }\n    },\n    openLight: async (_, { state, actions }) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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<typeof Model.actions>\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 () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button onClick={e => actions.increment(33)}>home increment</button>\n      <button onClick={e => sharedActions.increment(20)}>\n        shared increment\n      </button>\n      <button onClick={e => actions.get()}>fake request</button>\n      <button onClick={e => actions.openLight()}>fake nested call</button>\n    </div>\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 >= 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> Hint: The state returned should only be used as readonly\n\n```jsx\nimport { useStore, getState } from '../model/index'\n\nconst BasicHook = () => {\n  const [state, actions] = useStore('Counter')\n  useEffect(() => {\n    console.log('some mounted actions from BasicHooks')\n    return () =>\n      console.log(\n        `Basic Hooks unmounted, current Counter state: ${JSON.stringify(\n          getState('Counter')\n        )}`\n      )\n  }, [])\n  return (\n    <>\n      <div>state: {JSON.stringify(state)}</div>\n    </>\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: () => {\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 = () => {\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (params, { state: s }) => {\n      // return (state: typeof s) => { // TypeScript < 3.9\n      return state => {\n        state.counter += params || 1\n      }\n    },\n    decrease: params => s => {\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) => {\n      return state => {\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<details>\n<summary>Store: shared.ts</summary>\n<p>\n\n```ts\nconst initialState = {\n  counter: 0\n}\n\nconst model: ModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: (params, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    }\n  },\n  // Provide for SSR\n  asyncState: async context => {\n    await waitFor(4000)\n    return { counter: 500 }\n  },\n  state: initialState\n}\n\nexport default model\n```\n\n</p>\n</details>\n\n<details>\n<summary>Global Config: _app.tsx</summary>\n<p>\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) => {\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    <Container>\n      <Component {...pageProps} />\n    </Container>\n  )\n}\n\nMyApp.getInitialProps = async (context: NextAppContext) => {\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</p>\n</details>\n\n<details>\n<summary>Page: hooks/index.tsx</summary>\n<p>\n\n```tsx\nimport { useStore, getState } from '../index'\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button\n        onClick={e => {\n          actions.increment(33)\n        }}\n      >\n    </div>\n  )\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Single Page Config: benchmark.tsx</summary>\n<p>\n\n```tsx\n// ...\nBenchmark.getInitialProps = async () => {\n  return await getInitialState({ modelName: 'Todo' }, { isServer: true })\n}\n```\n</p>\n</details>\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<{}> = async (context, restMiddlewares) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: any) => 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) => async (params: any) => {\n  const context: Context = {\n    modelName,\n    setState,\n    actionName: action.name,\n    next: () => {},\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<\n  { name: string }, // State Type\n  { ext: undefined }, // ActionParamsType\n  { name: string } // ExtContextType\n> = {\n  actions: {\n    // { state, action } => { state, action, [name] }\n    ext: (_, { name }) => {\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 => '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      <Provider>\n        <Counter />\n      </Provider>\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<details>\n<summary>Javascript decorator version</summary>\n<p>\n\n```jsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\n\nconst mapProps = ({ light, counter }) => ({\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      <>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(5)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n      </>\n    )\n  }\n}\n```\n\n</p>\n</details>\n\n<details>\n<summary>TypeScript Version</summary>\n<p>\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) => ({\n  lightStatus: light ? 'open' : 'close',\n  counter,\n  response\n})\n\ntype RType = ReturnType<typeof mapProps>\n\nclass TSCounter extends PureComponent<\n  { state: RType } & { actions: ActionType }\n> {\n  render() {\n    const { state, actions } = this.props\n    return (\n      <>\n        <div>TS Counter</div>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(3)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n        <button onClick={e => actions.get()}>Get Response</button>\n        <div>message: {JSON.stringify(state.response)}</div>\n      </>\n    )\n  }\n}\n\nexport default connect(\n  'Home',\n  mapProps\n)(TSCounter)\n```\n</p>\n</details>\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<\n  CounterState,\n  CounterActionParams\n> = {\n  actions: {\n    increment: (params) => {\n      return (state) => {\n        state.count += params\n      }\n    }\n  },\n  state: { count: 0 }\n}\n\n// v4.1.x\nconst Counter = createStore(() => {\n  const [state, setState] = useModel({ count: 0 })\n  const actions = {\n    increment: (params) => {\n      setState((state) => {\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 = () => {\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<State, ActionParams> = {\n  state: {\n    counter: 1\n    enable: false\n  },\n  actions: {\n    add: (payload) => state => {\n      state.counter += payload\n    },\n    switch: () => state => {\n      state.enable = !state.enable\n    }\n  }\n}\n```\n\n```ts\nconst Component = () => {\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 => 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 }) => ['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) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: Error) => 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) => {\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      <div className=\"App\">\n        <Counter />\n        <Counter />\n        <Counter />\n      </div>\n    )\n  }\n}\n```\n\n<details>\n<summary>Counter model</summary>\n<p>\n\n```ts\ninterface State {\n  count: number\n}\n\ninterface ActionParams {\n  increment: number\n}\n\nconst model: ModelType<State, ActionParams> = {\n  state: {\n    count: 0\n  },\n  actions: {\n    increment: payload => {\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 => (state.count += payload)\n      return state => {\n        state.count += payload\n      }\n    }\n  }\n}\n\n```\n\n</p>\n</details>\n\n<details>\n<summary>Counter.tsx</summary>\n<p>\n\n```tsx\n\nconst Counter = () => {\n  const [{ useStore }] = useState(() => Model(model))\n  const [state, actions] = useStore()\n  return (\n    <div>\n      <div>{state.count}</div>\n      <button onClick={() => actions.increment(3)}>Increment</button>\n    </div>\n  )\n}\n\nexport default Counter\n```\n\n</p>\n</details>\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<ExpensiveState, ExpensiveActionParams> = {\n  state: {\n    moduleList: []\n  },\n  actions: {\n    setPreFreezedDataset: () => {\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 => {\n    // Not allowed\n    // return state => (state.count += payload)\n    return state => {\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) => {\n  await timeout(1000, {})\n  context.next(restMiddlewares)\n}\n\nconst nextCounterModel: ModelType<CounterState, NextCounterActionParams> = {\n  actions: {\n    add: num => {\n      return state => {\n        state.count += num\n      }\n    },\n    increment: async (num, { actions }) => {\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","readmeFilename":"README.md","gitHead":"279b60225ab15ba81abb43b95a8985ef4783d592","_id":"react-model@4.2.0-rc.0","_nodeVersion":"12.22.1","_npmVersion":"6.14.12","dist":{"integrity":"sha512-O4WXmMmzwd7dxzrdTy/lEuOL/fcODUb0g7rU51fsLvIxwfVR+7cNBriklQKspotI9pZLIHmq4LhK9BAz+5nY1A==","shasum":"63517f056c0dd5c1744a7a73a58d7e4449173788","tarball":"https://registry.npmjs.org/react-model/-/react-model-4.2.0-rc.0.tgz","fileCount":22,"unpackedSize":112457,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDw4Rd1uzbUSb8qB/gJBqJKqKTmT4Md7JhE9knc0UTxTwIgdntpTS0fUrUFaaUgbjIP7m+Jl7s4QsWRQKFk4pZfp8k="}]},"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"maintainers":[{"name":"leecade","email":"leecade@163.com"},{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_4.2.0-rc.0_1632810036587_0.23545601173498287"},"_hasShrinkwrap":false},"4.2.0-rc.1":{"name":"react-model","version":"4.2.0-rc.1","description":"The State management library for React","main":"./dist/react-model.js","module":"./dist/react-model.esm.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build:prod":"microbundle --define process.env.NODE_ENV=production --sourcemap false --jsx React.createElement --output dist --tsconfig ./tsconfig.json","build:dev":"microbundle --define process.env.NODE_ENV=development --sourcemap true --jsx React.createElement --output dist --tsconfig ./tsconfig.json","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":">=8.0.1"},"peerDependencies":{"react":">=16.3.0","react-dom":">=16.3.0","typescript":">=3.9.0"},"devDependencies":{"@commitlint/cli":"^8.1.0","@commitlint/config-conventional":"^8.1.0","@testing-library/react":"^10.0.1","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/faker":"^5.5.3","@types/jest":"^25.1.0","@types/node":"^14.0.0","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","faker":"^5.5.3","husky":"^4.0.2","jest":"^24.1.0","microbundle":"^0.12.3","prettier":"^2.0.0","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^8.0.0","remark-lint":"^7.0.0","remark-preset-lint-recommended":"^4.0.0","ts-jest":"^26.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"browserslist":["edge 17","firefox 70","chrome 48","safari 12.1","android 4.0","samsung 9.2"],"readme":"# react-model &middot; ![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) [![Build Status](https://travis-ci.org/byte-fe/react-model.svg?branch=master)](https://travis-ci.org/byte-fe/react-model) [![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 = () => {\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(Todo)\n\nconst App = () => {\n  return <TodoList />\n}\n\nconst TodoList = () => {\n  const { items, setItems } = useStore()\n  return <div>\n    <Addon handler={setItems} />\n    {state.items.map((item, index) => (<Todo key={index} item={item} />))}\n  </div>\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(() => {\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 = () => {\n    setLocalCount(localCount + 1)\n  }\n  const inc = () => {\n    setCount(c => 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 = () => {\n  const { count, localCount, inc, incLocal } = useStore()\n  return <div>\n    <span>count: { count }</span>\n    <span>localCount: { localCount }</span>\n    <button onClick={inc}>inc</button>\n    <button onClick={incLocal}>incLocal</button>\n  </div>\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (payload, { state }) => {\n      return {\n        counter: state.counter + (payload || 1)\n      }\n    },\n    openLight: async (_, { state, actions }) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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<typeof Model.actions>\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 () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button onClick={e => actions.increment(33)}>home increment</button>\n      <button onClick={e => sharedActions.increment(20)}>\n        shared increment\n      </button>\n      <button onClick={e => actions.get()}>fake request</button>\n      <button onClick={e => actions.openLight()}>fake nested call</button>\n    </div>\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 >= 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> Hint: The state returned should only be used as readonly\n\n```jsx\nimport { useStore, getState } from '../model/index'\n\nconst BasicHook = () => {\n  const [state, actions] = useStore('Counter')\n  useEffect(() => {\n    console.log('some mounted actions from BasicHooks')\n    return () =>\n      console.log(\n        `Basic Hooks unmounted, current Counter state: ${JSON.stringify(\n          getState('Counter')\n        )}`\n      )\n  }, [])\n  return (\n    <>\n      <div>state: {JSON.stringify(state)}</div>\n    </>\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: () => {\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 = () => {\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (params, { state: s }) => {\n      // return (state: typeof s) => { // TypeScript < 3.9\n      return state => {\n        state.counter += params || 1\n      }\n    },\n    decrease: params => s => {\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) => {\n      return state => {\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<details>\n<summary>Store: shared.ts</summary>\n<p>\n\n```ts\nconst initialState = {\n  counter: 0\n}\n\nconst model: ModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: (params, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    }\n  },\n  // Provide for SSR\n  asyncState: async context => {\n    await waitFor(4000)\n    return { counter: 500 }\n  },\n  state: initialState\n}\n\nexport default model\n```\n\n</p>\n</details>\n\n<details>\n<summary>Global Config: _app.tsx</summary>\n<p>\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) => {\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    <Container>\n      <Component {...pageProps} />\n    </Container>\n  )\n}\n\nMyApp.getInitialProps = async (context: NextAppContext) => {\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</p>\n</details>\n\n<details>\n<summary>Page: hooks/index.tsx</summary>\n<p>\n\n```tsx\nimport { useStore, getState } from '../index'\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button\n        onClick={e => {\n          actions.increment(33)\n        }}\n      >\n    </div>\n  )\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Single Page Config: benchmark.tsx</summary>\n<p>\n\n```tsx\n// ...\nBenchmark.getInitialProps = async () => {\n  return await getInitialState({ modelName: 'Todo' }, { isServer: true })\n}\n```\n</p>\n</details>\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<{}> = async (context, restMiddlewares) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: any) => 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) => async (params: any) => {\n  const context: Context = {\n    modelName,\n    setState,\n    actionName: action.name,\n    next: () => {},\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<\n  { name: string }, // State Type\n  { ext: undefined }, // ActionParamsType\n  { name: string } // ExtContextType\n> = {\n  actions: {\n    // { state, action } => { state, action, [name] }\n    ext: (_, { name }) => {\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 => '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      <Provider>\n        <Counter />\n      </Provider>\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<details>\n<summary>Javascript decorator version</summary>\n<p>\n\n```jsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\n\nconst mapProps = ({ light, counter }) => ({\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      <>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(5)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n      </>\n    )\n  }\n}\n```\n\n</p>\n</details>\n\n<details>\n<summary>TypeScript Version</summary>\n<p>\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) => ({\n  lightStatus: light ? 'open' : 'close',\n  counter,\n  response\n})\n\ntype RType = ReturnType<typeof mapProps>\n\nclass TSCounter extends PureComponent<\n  { state: RType } & { actions: ActionType }\n> {\n  render() {\n    const { state, actions } = this.props\n    return (\n      <>\n        <div>TS Counter</div>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(3)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n        <button onClick={e => actions.get()}>Get Response</button>\n        <div>message: {JSON.stringify(state.response)}</div>\n      </>\n    )\n  }\n}\n\nexport default connect(\n  'Home',\n  mapProps\n)(TSCounter)\n```\n</p>\n</details>\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<\n  CounterState,\n  CounterActionParams\n> = {\n  actions: {\n    increment: (params) => {\n      return (state) => {\n        state.count += params\n      }\n    }\n  },\n  state: { count: 0 }\n}\n\n// v4.1.x\nconst Counter = createStore(() => {\n  const [state, setState] = useModel({ count: 0 })\n  const actions = {\n    increment: (params) => {\n      setState((state) => {\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 = () => {\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<State, ActionParams> = {\n  state: {\n    counter: 1\n    enable: false\n  },\n  actions: {\n    add: (payload) => state => {\n      state.counter += payload\n    },\n    switch: () => state => {\n      state.enable = !state.enable\n    }\n  }\n}\n```\n\n```ts\nconst Component = () => {\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 => 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 }) => ['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) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: Error) => 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) => {\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      <div className=\"App\">\n        <Counter />\n        <Counter />\n        <Counter />\n      </div>\n    )\n  }\n}\n```\n\n<details>\n<summary>Counter model</summary>\n<p>\n\n```ts\ninterface State {\n  count: number\n}\n\ninterface ActionParams {\n  increment: number\n}\n\nconst model: ModelType<State, ActionParams> = {\n  state: {\n    count: 0\n  },\n  actions: {\n    increment: payload => {\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 => (state.count += payload)\n      return state => {\n        state.count += payload\n      }\n    }\n  }\n}\n\n```\n\n</p>\n</details>\n\n<details>\n<summary>Counter.tsx</summary>\n<p>\n\n```tsx\n\nconst Counter = () => {\n  const [{ useStore }] = useState(() => Model(model))\n  const [state, actions] = useStore()\n  return (\n    <div>\n      <div>{state.count}</div>\n      <button onClick={() => actions.increment(3)}>Increment</button>\n    </div>\n  )\n}\n\nexport default Counter\n```\n\n</p>\n</details>\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<ExpensiveState, ExpensiveActionParams> = {\n  state: {\n    moduleList: []\n  },\n  actions: {\n    setPreFreezedDataset: () => {\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 => {\n    // Not allowed\n    // return state => (state.count += payload)\n    return state => {\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) => {\n  await timeout(1000, {})\n  context.next(restMiddlewares)\n}\n\nconst nextCounterModel: ModelType<CounterState, NextCounterActionParams> = {\n  actions: {\n    add: num => {\n      return state => {\n        state.count += num\n      }\n    },\n    increment: async (num, { actions }) => {\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","readmeFilename":"README.md","gitHead":"279b60225ab15ba81abb43b95a8985ef4783d592","_id":"react-model@4.2.0-rc.1","_nodeVersion":"12.22.1","_npmVersion":"6.14.12","dist":{"integrity":"sha512-9rPGR3H/CXRdzRaEdsCuuaR/zn4/VgK16hE4/+vdL3t/AuElUntWCGlXkpb8gUcAenaTwIsJjU36hCZBi0SkRA==","shasum":"79efd78e81e65e08f674a50d1f350ec27de98a4a","tarball":"https://registry.npmjs.org/react-model/-/react-model-4.2.0-rc.1.tgz","fileCount":22,"unpackedSize":112483,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQCfsfPlJP+7zSNSgi5bQW7Yf3qWinvUeBw1vXHW/YmpGwIhAI7KURyge5pDOIU4krFam6m4qx6HiUpuoyApBYWV2SzD"}]},"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"maintainers":[{"name":"leecade","email":"leecade@163.com"},{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_4.2.0-rc.1_1632975748051_0.10334035216355009"},"_hasShrinkwrap":false},"4.2.0-rc.2":{"name":"react-model","version":"4.2.0-rc.2","description":"The State management library for React","main":"./dist/react-model.js","module":"./dist/react-model.esm.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build:prod":"microbundle --define process.env.NODE_ENV=production --sourcemap false --jsx React.createElement --output dist --tsconfig ./tsconfig.json","build:dev":"microbundle --define process.env.NODE_ENV=development --sourcemap true --jsx React.createElement --output dist --tsconfig ./tsconfig.json","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":">=8.0.1"},"peerDependencies":{"react":">=16.3.0","react-dom":">=16.3.0","typescript":">=3.9.0"},"devDependencies":{"@commitlint/cli":"^8.1.0","@commitlint/config-conventional":"^8.1.0","@testing-library/react":"^10.0.1","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/faker":"^5.5.3","@types/jest":"^25.1.0","@types/node":"^14.0.0","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","faker":"^5.5.3","husky":"^4.0.2","jest":"^24.1.0","microbundle":"^0.12.3","prettier":"^2.0.0","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^8.0.0","remark-lint":"^7.0.0","remark-preset-lint-recommended":"^4.0.0","ts-jest":"^26.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"browserslist":["edge 17","firefox 70","chrome 48","safari 12.1","android 4.0","samsung 9.2"],"readme":"# react-model &middot; ![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) [![Build Status](https://travis-ci.org/byte-fe/react-model.svg?branch=master)](https://travis-ci.org/byte-fe/react-model) [![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 = () => {\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(Todo)\n\nconst App = () => {\n  return <TodoList />\n}\n\nconst TodoList = () => {\n  const { items, setItems } = useStore()\n  return <div>\n    <Addon handler={setItems} />\n    {state.items.map((item, index) => (<Todo key={index} item={item} />))}\n  </div>\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(() => {\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 = () => {\n    setLocalCount(localCount + 1)\n  }\n  const inc = () => {\n    setCount(c => 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 = () => {\n  const { count, localCount, inc, incLocal } = useStore()\n  return <div>\n    <span>count: { count }</span>\n    <span>localCount: { localCount }</span>\n    <button onClick={inc}>inc</button>\n    <button onClick={incLocal}>incLocal</button>\n  </div>\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (payload, { state }) => {\n      return {\n        counter: state.counter + (payload || 1)\n      }\n    },\n    openLight: async (_, { state, actions }) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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<typeof Model.actions>\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 () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button onClick={e => actions.increment(33)}>home increment</button>\n      <button onClick={e => sharedActions.increment(20)}>\n        shared increment\n      </button>\n      <button onClick={e => actions.get()}>fake request</button>\n      <button onClick={e => actions.openLight()}>fake nested call</button>\n    </div>\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 >= 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> Hint: The state returned should only be used as readonly\n\n```jsx\nimport { useStore, getState } from '../model/index'\n\nconst BasicHook = () => {\n  const [state, actions] = useStore('Counter')\n  useEffect(() => {\n    console.log('some mounted actions from BasicHooks')\n    return () =>\n      console.log(\n        `Basic Hooks unmounted, current Counter state: ${JSON.stringify(\n          getState('Counter')\n        )}`\n      )\n  }, [])\n  return (\n    <>\n      <div>state: {JSON.stringify(state)}</div>\n    </>\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: () => {\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 = () => {\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (params, { state: s }) => {\n      // return (state: typeof s) => { // TypeScript < 3.9\n      return state => {\n        state.counter += params || 1\n      }\n    },\n    decrease: params => s => {\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) => {\n      return state => {\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<details>\n<summary>Store: shared.ts</summary>\n<p>\n\n```ts\nconst initialState = {\n  counter: 0\n}\n\nconst model: ModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: (params, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    }\n  },\n  // Provide for SSR\n  asyncState: async context => {\n    await waitFor(4000)\n    return { counter: 500 }\n  },\n  state: initialState\n}\n\nexport default model\n```\n\n</p>\n</details>\n\n<details>\n<summary>Global Config: _app.tsx</summary>\n<p>\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) => {\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    <Container>\n      <Component {...pageProps} />\n    </Container>\n  )\n}\n\nMyApp.getInitialProps = async (context: NextAppContext) => {\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</p>\n</details>\n\n<details>\n<summary>Page: hooks/index.tsx</summary>\n<p>\n\n```tsx\nimport { useStore, getState } from '../index'\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button\n        onClick={e => {\n          actions.increment(33)\n        }}\n      >\n    </div>\n  )\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Single Page Config: benchmark.tsx</summary>\n<p>\n\n```tsx\n// ...\nBenchmark.getInitialProps = async () => {\n  return await getInitialState({ modelName: 'Todo' }, { isServer: true })\n}\n```\n</p>\n</details>\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<{}> = async (context, restMiddlewares) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: any) => 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) => async (params: any) => {\n  const context: Context = {\n    modelName,\n    setState,\n    actionName: action.name,\n    next: () => {},\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<\n  { name: string }, // State Type\n  { ext: undefined }, // ActionParamsType\n  { name: string } // ExtContextType\n> = {\n  actions: {\n    // { state, action } => { state, action, [name] }\n    ext: (_, { name }) => {\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 => '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      <Provider>\n        <Counter />\n      </Provider>\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<details>\n<summary>Javascript decorator version</summary>\n<p>\n\n```jsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\n\nconst mapProps = ({ light, counter }) => ({\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      <>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(5)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n      </>\n    )\n  }\n}\n```\n\n</p>\n</details>\n\n<details>\n<summary>TypeScript Version</summary>\n<p>\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) => ({\n  lightStatus: light ? 'open' : 'close',\n  counter,\n  response\n})\n\ntype RType = ReturnType<typeof mapProps>\n\nclass TSCounter extends PureComponent<\n  { state: RType } & { actions: ActionType }\n> {\n  render() {\n    const { state, actions } = this.props\n    return (\n      <>\n        <div>TS Counter</div>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(3)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n        <button onClick={e => actions.get()}>Get Response</button>\n        <div>message: {JSON.stringify(state.response)}</div>\n      </>\n    )\n  }\n}\n\nexport default connect(\n  'Home',\n  mapProps\n)(TSCounter)\n```\n</p>\n</details>\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<\n  CounterState,\n  CounterActionParams\n> = {\n  actions: {\n    increment: (params) => {\n      return (state) => {\n        state.count += params\n      }\n    }\n  },\n  state: { count: 0 }\n}\n\n// v4.1.x\nconst Counter = createStore(() => {\n  const [state, setState] = useModel({ count: 0 })\n  const actions = {\n    increment: (params) => {\n      setState((state) => {\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 = () => {\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<State, ActionParams> = {\n  state: {\n    counter: 1\n    enable: false\n  },\n  actions: {\n    add: (payload) => state => {\n      state.counter += payload\n    },\n    switch: () => state => {\n      state.enable = !state.enable\n    }\n  }\n}\n```\n\n```ts\nconst Component = () => {\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 => 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 }) => ['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) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: Error) => 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) => {\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      <div className=\"App\">\n        <Counter />\n        <Counter />\n        <Counter />\n      </div>\n    )\n  }\n}\n```\n\n<details>\n<summary>Counter model</summary>\n<p>\n\n```ts\ninterface State {\n  count: number\n}\n\ninterface ActionParams {\n  increment: number\n}\n\nconst model: ModelType<State, ActionParams> = {\n  state: {\n    count: 0\n  },\n  actions: {\n    increment: payload => {\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 => (state.count += payload)\n      return state => {\n        state.count += payload\n      }\n    }\n  }\n}\n\n```\n\n</p>\n</details>\n\n<details>\n<summary>Counter.tsx</summary>\n<p>\n\n```tsx\n\nconst Counter = () => {\n  const [{ useStore }] = useState(() => Model(model))\n  const [state, actions] = useStore()\n  return (\n    <div>\n      <div>{state.count}</div>\n      <button onClick={() => actions.increment(3)}>Increment</button>\n    </div>\n  )\n}\n\nexport default Counter\n```\n\n</p>\n</details>\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<ExpensiveState, ExpensiveActionParams> = {\n  state: {\n    moduleList: []\n  },\n  actions: {\n    setPreFreezedDataset: () => {\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 => {\n    // Not allowed\n    // return state => (state.count += payload)\n    return state => {\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) => {\n  await timeout(1000, {})\n  context.next(restMiddlewares)\n}\n\nconst nextCounterModel: ModelType<CounterState, NextCounterActionParams> = {\n  actions: {\n    add: num => {\n      return state => {\n        state.count += num\n      }\n    },\n    increment: async (num, { actions }) => {\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","readmeFilename":"README.md","gitHead":"6fda5fae1971dc92ac0b556693b471d6f3918f72","_id":"react-model@4.2.0-rc.2","_nodeVersion":"12.22.1","_npmVersion":"6.14.12","dist":{"integrity":"sha512-Pu7SzNjsaSvnXzVaGp+epDHkb3JtNRImhxEnsS6L7LGDmBnAc0tOxEWZ26SToQykbH8c/GmG3RPbMYL8fGNXzA==","shasum":"50a26ee9e3d752688885829b2b23cb0d6e799770","tarball":"https://registry.npmjs.org/react-model/-/react-model-4.2.0-rc.2.tgz","fileCount":22,"unpackedSize":112781,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhk62oCRA9TVsSAnZWagAAKBkP/2VOMm8+/gBpDCDvEUVm\n6NjzTsx1vURee1S+JS4ieanKWLijWIqj18W13HUlLQ63rjJPmQ0t82qQLeeZ\npqFmVqwdmKfcM7gS8CeA9vwOuY0auzZYkl1n1XDrremWhcugG90VhEd0GqZ5\nZ/ElY/prdfT5Wu09pil22kzt1ijkCj1uyAnt3z+YAorOdED6y+wJ4DvnTm0O\nc9YPE5MRieWXB/koQHcQkRGSpxsgAhOD3YWxlhw6PvOkxRoLnHtf8CUw+mBH\nhI4020WN+soibSOxedFg4AE27UqfEnQafYwgPmQePLZ0YkUlG8RfH/SHKAii\nA5O3RfXAraOjArfumpAKVG9i4WgVrfkjx3ZcI08KMunEGpUgl16FfyUvvLpi\njsCRAylUNFQfib3E/7hKjtc4QBV4i3CwbyhQ+KnnqkWtox28e5mdEXKvOlyv\nXGgjzcUKOeHonCxYMbE3h7GkiaVEJbJyLTWD6Dsd7CPN1uChX4eMbZDCYEPR\nX8s8K8X9TDbSSndRHAOlh43Ic9z83AYzskDYCMXjvMa76+F4VEB9q6qhsfM8\n+4BzuYFQ+UYO1Hx+3wdZZ9cUE1rb0QyNHTRh8vc0IoXX5QW77+KTubUT5E4Y\nTiMimraxDpq3stABpqT8bm/S4QOS29AOYQSva/ZbTvmWkq2PIm9m/FB7payR\nsydh\r\n=9WKl\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQD9gFub3paNQwInK/vYyDIPbe1EvrnCu13sXATmDI9qTQIhAKMCDwCNxJYpjVLi4WRCbEAIZDvrYX6jrS4/9PcS7Ma0"}]},"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"maintainers":[{"name":"leecade","email":"leecade@163.com"},{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_4.2.0-rc.2_1637068200707_0.18522298382824132"},"_hasShrinkwrap":false},"4.2.0":{"name":"react-model","version":"4.2.0","description":"The State management library for React","main":"./dist/react-model.js","module":"./dist/react-model.esm.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build:prod":"microbundle --define process.env.NODE_ENV=production --sourcemap false --jsx React.createElement --output dist --tsconfig ./tsconfig.json","build:dev":"microbundle --define process.env.NODE_ENV=development --sourcemap true --jsx React.createElement --output dist --tsconfig ./tsconfig.json","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":">=8.0.1"},"peerDependencies":{"react":">=16.3.0","react-dom":">=16.3.0","typescript":">=3.9.0"},"devDependencies":{"@commitlint/cli":"^8.1.0","@commitlint/config-conventional":"^8.1.0","@testing-library/react":"^10.0.1","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/faker":"^5.5.3","@types/jest":"^25.1.0","@types/node":"^14.0.0","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","faker":"^5.5.3","husky":"^4.0.2","jest":"^24.1.0","microbundle":"^0.12.3","prettier":"^2.0.0","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^8.0.0","remark-lint":"^7.0.0","remark-preset-lint-recommended":"^4.0.0","ts-jest":"^26.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"browserslist":["edge 17","firefox 70","chrome 48","safari 12.1","android 4.0","samsung 9.2"],"gitHead":"6fda5fae1971dc92ac0b556693b471d6f3918f72","_id":"react-model@4.2.0","_nodeVersion":"12.22.1","_npmVersion":"6.14.12","dist":{"integrity":"sha512-hgYz/8RYYsoXhv+NJpGj0jM2TJV8jghLrxQ6S+E+NgNZL0D+71VmYzfSHzscaR1cpY4Kzqvi7LtTPP7miXj5Wg==","shasum":"d96818bbb49b2f1d5691a642a8040b3165db9715","tarball":"https://registry.npmjs.org/react-model/-/react-model-4.2.0.tgz","fileCount":22,"unpackedSize":112776,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhk7KWCRA9TVsSAnZWagAA5W8P/1j15389djICnvCFOmtX\nSDGQsTC5pbEzT7WeST695ZR3bBJ2mmXP1I4p7OhpkV/rYnqtk5aXX+bHIHPX\nuaHYVM+cVVcyNxKUekBAbwyds/n+9iccg93vd4AX/egmTGahtbjW98KQPPQz\n4rxZzxR8jmyXYk9haRjURDDQVv/qqptRHY5yOTKqKwWmiF4vLzSl8XVPjE+i\ne099dlrhCE4llHRPS+oHRhoiYY/ZHzHzIGdH0hRrbpulChAoSMu7vZFTRHCL\n+R5cpFl+VDo9SI2avDmANdncjRSnLve45LmAJ/2ujHjhyIyg9rWYxM0jkGY+\nKQ1EEo+dOh+AlcigLzERjUnZdhD5XJyZvl9Xet/MiQDdK8ukeLXIRrzPJHc5\nylXRtWXclwJUPkaKZ0k+pA5TZdF6xiqAPtTDUhH5KOdVpgtCCZImADrfOiBl\nG+4msL8DuFj4LhhkmCK2aXgaG3OImtWB8ZFygY14sESjqQqi+wJXs4K6eUUf\noKiHt4sHUFcWXKRff2Y8YmGfv+rX1gKg5OshDK8TCATgs6FkC+R6YJ9bqit1\nCQDlBiDbtrLLfEK5hHcWNC8DX5UWBgvI9emGNHLDvXu8nICa2oe7/4teWCJ2\nwtWxz36ryZeB+vaK7TmIxX7YYAKVhxMl1TRdDqJOKgj4cdeXvofibeEh7gxi\nDVTO\r\n=T7rz\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQCoMsoC40uPAc7n349kCsQFOJSdUE3IqL6Hqe4h8+I2tgIhAO4Z3IX3rQGJ7OV/jnAUZycmWdZYtIdBBLG4lF23l0dP"}]},"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"maintainers":[{"name":"leecade","email":"leecade@163.com"},{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_4.2.0_1637069462500_0.02362793837037569"},"_hasShrinkwrap":false},"4.3.0-alpha.0":{"name":"react-model","version":"4.3.0-alpha.0","description":"The State management library for React","main":"./dist/react-model.js","module":"./dist/react-model.esm.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build:prod":"microbundle --define process.env.NODE_ENV=production --sourcemap false --jsx React.createElement --output dist --tsconfig ./tsconfig.json","build:dev":"microbundle --define process.env.NODE_ENV=development --sourcemap true --jsx React.createElement --output dist --tsconfig ./tsconfig.json","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":">=8.0.1"},"peerDependencies":{"react":">=16.3.0","react-dom":">=16.3.0","typescript":">=3.9.0"},"devDependencies":{"@commitlint/cli":"^8.1.0","@commitlint/config-conventional":"^8.1.0","@testing-library/react":"^10.0.1","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/faker":"^5.5.3","@types/jest":"^25.1.0","@types/node":"^14.0.0","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","faker":"^5.5.3","husky":"^4.0.2","jest":"^24.1.0","microbundle":"^0.12.3","prettier":"^2.0.0","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^8.0.0","remark-lint":"^7.0.0","remark-preset-lint-recommended":"^4.0.0","ts-jest":"^26.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"browserslist":["edge 17","firefox 70","chrome 48","safari 12.1","android 4.0","samsung 9.2"],"readme":"# react-model &middot; ![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) [![Build Status](https://travis-ci.org/byte-fe/react-model.svg?branch=master)](https://travis-ci.org/byte-fe/react-model) [![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 = () => {\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(Todo)\n\nconst App = () => {\n  return <TodoList />\n}\n\nconst TodoList = () => {\n  const { items, setItems } = useStore()\n  return <div>\n    <Addon handler={setItems} />\n    {state.items.map((item, index) => (<Todo key={index} item={item} />))}\n  </div>\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(() => {\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 = () => {\n    setLocalCount(localCount + 1)\n  }\n  const inc = () => {\n    setCount(c => 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 = () => {\n  const { count, localCount, inc, incLocal } = useStore()\n  return <div>\n    <span>count: { count }</span>\n    <span>localCount: { localCount }</span>\n    <button onClick={inc}>inc</button>\n    <button onClick={incLocal}>incLocal</button>\n  </div>\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (payload, { state }) => {\n      return {\n        counter: state.counter + (payload || 1)\n      }\n    },\n    openLight: async (_, { state, actions }) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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<typeof Model.actions>\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 () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button onClick={e => actions.increment(33)}>home increment</button>\n      <button onClick={e => sharedActions.increment(20)}>\n        shared increment\n      </button>\n      <button onClick={e => actions.get()}>fake request</button>\n      <button onClick={e => actions.openLight()}>fake nested call</button>\n    </div>\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 >= 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> Hint: The state returned should only be used as readonly\n\n```jsx\nimport { useStore, getState } from '../model/index'\n\nconst BasicHook = () => {\n  const [state, actions] = useStore('Counter')\n  useEffect(() => {\n    console.log('some mounted actions from BasicHooks')\n    return () =>\n      console.log(\n        `Basic Hooks unmounted, current Counter state: ${JSON.stringify(\n          getState('Counter')\n        )}`\n      )\n  }, [])\n  return (\n    <>\n      <div>state: {JSON.stringify(state)}</div>\n    </>\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: () => {\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 = () => {\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (params, { state: s }) => {\n      // return (state: typeof s) => { // TypeScript < 3.9\n      return state => {\n        state.counter += params || 1\n      }\n    },\n    decrease: params => s => {\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) => {\n      return state => {\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<details>\n<summary>Store: shared.ts</summary>\n<p>\n\n```ts\nconst initialState = {\n  counter: 0\n}\n\nconst model: ModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: (params, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    }\n  },\n  // Provide for SSR\n  asyncState: async context => {\n    await waitFor(4000)\n    return { counter: 500 }\n  },\n  state: initialState\n}\n\nexport default model\n```\n\n</p>\n</details>\n\n<details>\n<summary>Global Config: _app.tsx</summary>\n<p>\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) => {\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    <Container>\n      <Component {...pageProps} />\n    </Container>\n  )\n}\n\nMyApp.getInitialProps = async (context: NextAppContext) => {\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</p>\n</details>\n\n<details>\n<summary>Page: hooks/index.tsx</summary>\n<p>\n\n```tsx\nimport { useStore, getState } from '../index'\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button\n        onClick={e => {\n          actions.increment(33)\n        }}\n      >\n    </div>\n  )\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Single Page Config: benchmark.tsx</summary>\n<p>\n\n```tsx\n// ...\nBenchmark.getInitialProps = async () => {\n  return await getInitialState({ modelName: 'Todo' }, { isServer: true })\n}\n```\n</p>\n</details>\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<{}> = async (context, restMiddlewares) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: any) => 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) => async (params: any) => {\n  const context: Context = {\n    modelName,\n    setState,\n    actionName: action.name,\n    next: () => {},\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<\n  { name: string }, // State Type\n  { ext: undefined }, // ActionParamsType\n  { name: string } // ExtContextType\n> = {\n  actions: {\n    // { state, action } => { state, action, [name] }\n    ext: (_, { name }) => {\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 => '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      <Provider>\n        <Counter />\n      </Provider>\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<details>\n<summary>Javascript decorator version</summary>\n<p>\n\n```jsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\n\nconst mapProps = ({ light, counter }) => ({\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      <>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(5)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n      </>\n    )\n  }\n}\n```\n\n</p>\n</details>\n\n<details>\n<summary>TypeScript Version</summary>\n<p>\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) => ({\n  lightStatus: light ? 'open' : 'close',\n  counter,\n  response\n})\n\ntype RType = ReturnType<typeof mapProps>\n\nclass TSCounter extends PureComponent<\n  { state: RType } & { actions: ActionType }\n> {\n  render() {\n    const { state, actions } = this.props\n    return (\n      <>\n        <div>TS Counter</div>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(3)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n        <button onClick={e => actions.get()}>Get Response</button>\n        <div>message: {JSON.stringify(state.response)}</div>\n      </>\n    )\n  }\n}\n\nexport default connect(\n  'Home',\n  mapProps\n)(TSCounter)\n```\n</p>\n</details>\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<\n  CounterState,\n  CounterActionParams\n> = {\n  actions: {\n    increment: (params) => {\n      return (state) => {\n        state.count += params\n      }\n    }\n  },\n  state: { count: 0 }\n}\n\n// v4.1.x\nconst Counter = createStore(() => {\n  const [state, setState] = useModel({ count: 0 })\n  const actions = {\n    increment: (params) => {\n      setState((state) => {\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 = () => {\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<State, ActionParams> = {\n  state: {\n    counter: 1\n    enable: false\n  },\n  actions: {\n    add: (payload) => state => {\n      state.counter += payload\n    },\n    switch: () => state => {\n      state.enable = !state.enable\n    }\n  }\n}\n```\n\n```ts\nconst Component = () => {\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 => 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 }) => ['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) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: Error) => 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) => {\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      <div className=\"App\">\n        <Counter />\n        <Counter />\n        <Counter />\n      </div>\n    )\n  }\n}\n```\n\n<details>\n<summary>Counter model</summary>\n<p>\n\n```ts\ninterface State {\n  count: number\n}\n\ninterface ActionParams {\n  increment: number\n}\n\nconst model: ModelType<State, ActionParams> = {\n  state: {\n    count: 0\n  },\n  actions: {\n    increment: payload => {\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 => (state.count += payload)\n      return state => {\n        state.count += payload\n      }\n    }\n  }\n}\n\n```\n\n</p>\n</details>\n\n<details>\n<summary>Counter.tsx</summary>\n<p>\n\n```tsx\n\nconst Counter = () => {\n  const [{ useStore }] = useState(() => Model(model))\n  const [state, actions] = useStore()\n  return (\n    <div>\n      <div>{state.count}</div>\n      <button onClick={() => actions.increment(3)}>Increment</button>\n    </div>\n  )\n}\n\nexport default Counter\n```\n\n</p>\n</details>\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<ExpensiveState, ExpensiveActionParams> = {\n  state: {\n    moduleList: []\n  },\n  actions: {\n    setPreFreezedDataset: () => {\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 => {\n    // Not allowed\n    // return state => (state.count += payload)\n    return state => {\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) => {\n  await timeout(1000, {})\n  context.next(restMiddlewares)\n}\n\nconst nextCounterModel: ModelType<CounterState, NextCounterActionParams> = {\n  actions: {\n    add: num => {\n      return state => {\n        state.count += num\n      }\n    },\n    increment: async (num, { actions }) => {\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","readmeFilename":"README.md","gitHead":"45a9fdbb67464a45e3b8e0e787726aa2bb345d04","_id":"react-model@4.3.0-alpha.0","_nodeVersion":"12.22.1","_npmVersion":"6.14.12","dist":{"integrity":"sha512-1VBE96AFvEhcHA0EGl5qIAaWAhVw3dxIBHS6XJJW6J9Mlw0qMbi77oeuVVVSi+ycp4NSJzR/UdUMi5SvglVVPA==","shasum":"1de7585d3e17db995bfe9e2495acf91a45d165cd","tarball":"https://registry.npmjs.org/react-model/-/react-model-4.3.0-alpha.0.tgz","fileCount":22,"unpackedSize":120112,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhy90NCRA9TVsSAnZWagAA4p4P/jyBIWzoTMdemYhxNq+T\nCuVqXhnc0CHahm3Bk5TrwpzjXTsXyI9ox3AZDY9cL+wf8bUe+ecAMqJ/y8LL\nqbRDslWVo2XIgwLc4tcSWI01WWVfTAwqX/1QC92pMGR2won34+IkYMLQB+17\nXIpgEvoj4uuq6OScZXav8v9nZu8mwf1/1+EciUvdpB1CPunZUPZwUZcOmoEZ\nVKk3NGJkpgAv2Cee1bY0WbRYAWjGousGbtbIPVS1Pjq9XFDc017Jy3t/Ay4k\nyt6UnQ5zNrdFt8Du1J8dukAMSyFzCmYvmjIgCQ1GUOx5Mzrrq9YaK9zNcSzv\nDFwmglYRJK5mazxKK7o4xDjJKudhYZXMTum/QckAZnA3VrXCbrCZFwZu5bXI\n53Z/JCt3DETgGXN9l5DSB9ERAwGMft9P9CbI0Fb5eLw4bPRpcYSw1m3h3VPK\nRc7o3p9bjVXzlxsQu7BTPX5kNXzyHvyaVmFr7QYrlDBE1ixEeF1uzEmkMpPQ\nZARe0hECEHVZWuCYeFq9V0E/EBpdfKaG359ClLIqg0p6mTiNCrJR6gaNfWgf\noEgfx+d8fK8JhwRcLQ0kl5644HFvJdxqxAONa4mWt5AyYokbl+VnWhXqwnXu\nOoNdm+Sru5fdgaTNpWH6jW4l5KIjflAN3Eyb0my8vDApp+Q21Bckz5eDyBNG\ncLka\r\n=aqUd\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDXxzbHDYh1Z1iuBqpm/Ri7BKNYuSzBXdOjDIgtctYAnAIgEtjEF3HkAXrTRb+oHkOOmoH/UT7P40bkv7dv8fvCvp4="}]},"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"maintainers":[{"name":"leecade","email":"leecade@163.com"},{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_4.3.0-alpha.0_1640750349221_0.9033418747664332"},"_hasShrinkwrap":false},"5.0.0-alpha.0":{"name":"react-model","version":"5.0.0-alpha.0","description":"The State management library for React","main":"./dist/react-model.js","module":"./dist/react-model.esm.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build:prod":"microbundle --define process.env.NODE_ENV=production --sourcemap false --jsx React.createElement --output dist --tsconfig ./tsconfig.json","build:dev":"microbundle --define process.env.NODE_ENV=development --sourcemap true --jsx React.createElement --output dist --tsconfig ./tsconfig.json","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":">=8.0.1"},"peerDependencies":{"react":">=16.3.0","react-dom":">=16.3.0","typescript":">=3.9.0"},"devDependencies":{"@commitlint/cli":"^8.1.0","@commitlint/config-conventional":"^8.1.0","@testing-library/react":"^10.0.1","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/faker":"^5.5.3","@types/jest":"^25.1.0","@types/node":"^14.0.0","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","faker":"^5.5.3","husky":"^4.0.2","jest":"^24.1.0","microbundle":"^0.12.3","prettier":"^2.0.0","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^8.0.0","remark-lint":"^7.0.0","remark-preset-lint-recommended":"^4.0.0","ts-jest":"^26.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"browserslist":["edge 17","firefox 70","chrome 48","safari 12.1","android 4.0","samsung 9.2"],"readme":"# react-model &middot; ![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) [![Build Status](https://travis-ci.org/byte-fe/react-model.svg?branch=master)](https://travis-ci.org/byte-fe/react-model) [![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 { model, createStore } from 'react-model'\n\n// define model\nconst useTodo = () => {\n  const [items, setItems] = model(['Install react-model', 'Read github docs', 'Build App'])\n  return { items, setItems }\n}\n\n// Model Register\nconst { useStore } = createStore(Todo)\n\nconst App = () => {\n  return <TodoList />\n}\n\nconst TodoList = () => {\n  const { items, setItems } = useStore()\n  return <div>\n    <Addon handler={setItems} />\n    {state.items.map((item, index) => (<Todo key={index} item={item} />))}\n  </div>\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 + model](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 { model } from 'react-model'\nconst { useStore } = createStore(() => {\n  const [localCount, setLocalCount] = useState(1) // Local State, Independent in different components\n  const [count, setCount] = model(1) // Global State, the value is the same in different components\n  const incLocal = () => {\n    setLocalCount(localCount + 1)\n  }\n  const inc = () => {\n    setCount(c => 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 = () => {\n  const { count, localCount, inc, incLocal } = useStore()\n  return <div>\n    <span>count: { count }</span>\n    <span>localCount: { localCount }</span>\n    <button onClick={inc}>inc</button>\n    <button onClick={incLocal}>incLocal</button>\n  </div>\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (payload, { state }) => {\n      return {\n        counter: state.counter + (payload || 1)\n      }\n    },\n    openLight: async (_, { state, actions }) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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<typeof Model.actions>\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 () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button onClick={e => actions.increment(33)}>home increment</button>\n      <button onClick={e => sharedActions.increment(20)}>\n        shared increment\n      </button>\n      <button onClick={e => actions.get()}>fake request</button>\n      <button onClick={e => actions.openLight()}>fake nested call</button>\n    </div>\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 >= 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> Hint: The state returned should only be used as readonly\n\n```jsx\nimport { useStore, getState } from '../model/index'\n\nconst BasicHook = () => {\n  const [state, actions] = useStore('Counter')\n  useEffect(() => {\n    console.log('some mounted actions from BasicHooks')\n    return () =>\n      console.log(\n        `Basic Hooks unmounted, current Counter state: ${JSON.stringify(\n          getState('Counter')\n        )}`\n      )\n  }, [])\n  return (\n    <>\n      <div>state: {JSON.stringify(state)}</div>\n    </>\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: () => {\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 = () => {\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (params, { state: s }) => {\n      // return (state: typeof s) => { // TypeScript < 3.9\n      return state => {\n        state.counter += params || 1\n      }\n    },\n    decrease: params => s => {\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) => {\n      return state => {\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<details>\n<summary>Store: shared.ts</summary>\n<p>\n\n```ts\nconst initialState = {\n  counter: 0\n}\n\nconst model: ModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: (params, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    }\n  },\n  // Provide for SSR\n  asyncState: async context => {\n    await waitFor(4000)\n    return { counter: 500 }\n  },\n  state: initialState\n}\n\nexport default model\n```\n\n</p>\n</details>\n\n<details>\n<summary>Global Config: _app.tsx</summary>\n<p>\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) => {\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    <Container>\n      <Component {...pageProps} />\n    </Container>\n  )\n}\n\nMyApp.getInitialProps = async (context: NextAppContext) => {\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</p>\n</details>\n\n<details>\n<summary>Page: hooks/index.tsx</summary>\n<p>\n\n```tsx\nimport { useStore, getState } from '../index'\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button\n        onClick={e => {\n          actions.increment(33)\n        }}\n      >\n    </div>\n  )\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Single Page Config: benchmark.tsx</summary>\n<p>\n\n```tsx\n// ...\nBenchmark.getInitialProps = async () => {\n  return await getInitialState({ modelName: 'Todo' }, { isServer: true })\n}\n```\n</p>\n</details>\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<{}> = async (context, restMiddlewares) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: any) => 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) => async (params: any) => {\n  const context: Context = {\n    modelName,\n    setState,\n    actionName: action.name,\n    next: () => {},\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<\n  { name: string }, // State Type\n  { ext: undefined }, // ActionParamsType\n  { name: string } // ExtContextType\n> = {\n  actions: {\n    // { state, action } => { state, action, [name] }\n    ext: (_, { name }) => {\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 => '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      <Provider>\n        <Counter />\n      </Provider>\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<details>\n<summary>Javascript decorator version</summary>\n<p>\n\n```jsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\n\nconst mapProps = ({ light, counter }) => ({\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      <>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(5)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n      </>\n    )\n  }\n}\n```\n\n</p>\n</details>\n\n<details>\n<summary>TypeScript Version</summary>\n<p>\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) => ({\n  lightStatus: light ? 'open' : 'close',\n  counter,\n  response\n})\n\ntype RType = ReturnType<typeof mapProps>\n\nclass TSCounter extends PureComponent<\n  { state: RType } & { actions: ActionType }\n> {\n  render() {\n    const { state, actions } = this.props\n    return (\n      <>\n        <div>TS Counter</div>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(3)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n        <button onClick={e => actions.get()}>Get Response</button>\n        <div>message: {JSON.stringify(state.response)}</div>\n      </>\n    )\n  }\n}\n\nexport default connect(\n  'Home',\n  mapProps\n)(TSCounter)\n```\n</p>\n</details>\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<\n  CounterState,\n  CounterActionParams\n> = {\n  actions: {\n    increment: (params) => {\n      return (state) => {\n        state.count += params\n      }\n    }\n  },\n  state: { count: 0 }\n}\n\n// v4.1.x\nconst Counter = createStore(() => {\n  const [state, setState] = model({ count: 0 })\n  const actions = {\n    increment: (params) => {\n      setState((state) => {\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 = () => {\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<State, ActionParams> = {\n  state: {\n    counter: 1\n    enable: false\n  },\n  actions: {\n    add: (payload) => state => {\n      state.counter += payload\n    },\n    switch: () => state => {\n      state.enable = !state.enable\n    }\n  }\n}\n```\n\n```ts\nconst Component = () => {\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 => 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 }) => ['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) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: Error) => 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) => {\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      <div className=\"App\">\n        <Counter />\n        <Counter />\n        <Counter />\n      </div>\n    )\n  }\n}\n```\n\n<details>\n<summary>Counter model</summary>\n<p>\n\n```ts\ninterface State {\n  count: number\n}\n\ninterface ActionParams {\n  increment: number\n}\n\nconst model: ModelType<State, ActionParams> = {\n  state: {\n    count: 0\n  },\n  actions: {\n    increment: payload => {\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 => (state.count += payload)\n      return state => {\n        state.count += payload\n      }\n    }\n  }\n}\n\n```\n\n</p>\n</details>\n\n<details>\n<summary>Counter.tsx</summary>\n<p>\n\n```tsx\n\nconst Counter = () => {\n  const [{ useStore }] = useState(() => Model(model))\n  const [state, actions] = useStore()\n  return (\n    <div>\n      <div>{state.count}</div>\n      <button onClick={() => actions.increment(3)}>Increment</button>\n    </div>\n  )\n}\n\nexport default Counter\n```\n\n</p>\n</details>\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<ExpensiveState, ExpensiveActionParams> = {\n  state: {\n    moduleList: []\n  },\n  actions: {\n    setPreFreezedDataset: () => {\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 => {\n    // Not allowed\n    // return state => (state.count += payload)\n    return state => {\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) => {\n  await timeout(1000, {})\n  context.next(restMiddlewares)\n}\n\nconst nextCounterModel: ModelType<CounterState, NextCounterActionParams> = {\n  actions: {\n    add: num => {\n      return state => {\n        state.count += num\n      }\n    },\n    increment: async (num, { actions }) => {\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","readmeFilename":"README.md","gitHead":"c9e8afbddacc618a159a62ae34307f5440ec2bdf","_id":"react-model@5.0.0-alpha.0","_nodeVersion":"12.22.1","_npmVersion":"6.14.12","dist":{"integrity":"sha512-8yKXTs2lXpAZ8WAvX/1BLw6PyO8NagdqvLg8drOiFCBMLCK4G5zLwGh7YTM8KjMgc4dvFTOqJLStGYReXVLWCg==","shasum":"20e42a837f4e303126e9966214ab53e7dd8b75ea","tarball":"https://registry.npmjs.org/react-model/-/react-model-5.0.0-alpha.0.tgz","fileCount":22,"unpackedSize":120066,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhy/q+CRA9TVsSAnZWagAAo5gP/AouxVU8odgph5bjutgm\nXsEMcwfR3hr2QycFqP1SKOalS6lf7I/bl4D+7W5kBU8prqVctuuH3uQUW3/N\nay14F7G/2GzOQbA9vkEEHF2ppAd5xIg6W2JuMCUckP879OaTG2TXCAT5YmEy\nTBTdf4070WwjxIS6g/7JAtE11xuehLPmCX08AhkiFL7DmJZmGyWojAEGY39O\nfYI0ryqJEj/h9tRJ+BxENfjWGOl0rSOEBJiGdNL83N84U3dVeIVHKAqYmDiR\nc3bdA1jYjMqE5MotyNXv+rFN2SMLz8oiZdkon7q3CIwnChCB8pgeXOKuULIW\n4e/PKJmhApjoDPWy0hmQuLKXjOCRyjoYX4nEe8s7OOLU0uVDL2SGWszujwFM\nS8WHZ2LEuiOXUzrBWsRUEG9sfot/Es//hPtSrNsGUv/A0cUZoM/DqcTqbISW\noS8Z7vqp5qEMdraMgJGCU+Vkr1SHY2GmVoejF9e2BwrgBxEjB48FD/LovoEv\nG9WhTlV5fZ592/w+QML8tPDGr8/v7ANbqXJgSIN+EL6t48gl2LVyiU2wGE6e\n1c9QWYTCcR2sz19BwdDF964px3AIWdSJNq5Kjae9gAP1Fc42RZGluZb3Oe0E\nndQ1Dt3e8ti6N4l+qeYfOgdkJnJTho1VzTP7piMrZHiqkRg3pI0EYqGoKYEV\n6QnG\r\n=mt9U\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQC0whTK1OmLqKwcfOJfuGgnvZOjimvMYdPhb1nhndRE2gIhALKEhX/+Lhr/3wAh7Fd7fSrGNb8fgXX2tkMPak4A/C55"}]},"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"maintainers":[{"name":"leecade","email":"leecade@163.com"},{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_5.0.0-alpha.0_1640757950428_0.6913603988476993"},"_hasShrinkwrap":false},"4.2.1-rc.0":{"name":"react-model","version":"4.2.1-rc.0","description":"The State management library for React","main":"./dist/react-model.js","module":"./dist/react-model.esm.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build:prod":"microbundle --define process.env.NODE_ENV=production --sourcemap false --jsx React.createElement --output dist --tsconfig ./tsconfig.json","build:dev":"microbundle --define process.env.NODE_ENV=development --sourcemap true --jsx React.createElement --output dist --tsconfig ./tsconfig.json","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":">=8.0.1"},"peerDependencies":{"react":">=16.3.0","react-dom":">=16.3.0","typescript":">=3.9.0"},"devDependencies":{"@commitlint/cli":"^8.1.0","@commitlint/config-conventional":"^8.1.0","@testing-library/react":"^10.0.1","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/faker":"^5.5.3","@types/jest":"^25.1.0","@types/node":"^14.0.0","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","faker":"^5.5.3","husky":"^4.0.2","jest":"^24.1.0","microbundle":"^0.12.3","prettier":"^2.0.0","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^8.0.0","remark-lint":"^7.0.0","remark-preset-lint-recommended":"^4.0.0","ts-jest":"^26.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"browserslist":["edge 17","firefox 70","chrome 48","safari 12.1","android 4.0","samsung 9.2"],"readme":"# react-model &middot; ![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) [![Build Status](https://travis-ci.org/byte-fe/react-model.svg?branch=master)](https://travis-ci.org/byte-fe/react-model) [![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 = () => {\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(Todo)\n\nconst App = () => {\n  return <TodoList />\n}\n\nconst TodoList = () => {\n  const { items, setItems } = useStore()\n  return <div>\n    <Addon handler={setItems} />\n    {state.items.map((item, index) => (<Todo key={index} item={item} />))}\n  </div>\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(() => {\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 = () => {\n    setLocalCount(localCount + 1)\n  }\n  const inc = () => {\n    setCount(c => 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 = () => {\n  const { count, localCount, inc, incLocal } = useStore()\n  return <div>\n    <span>count: { count }</span>\n    <span>localCount: { localCount }</span>\n    <button onClick={inc}>inc</button>\n    <button onClick={incLocal}>incLocal</button>\n  </div>\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (payload, { state }) => {\n      return {\n        counter: state.counter + (payload || 1)\n      }\n    },\n    openLight: async (_, { state, actions }) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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<typeof Model.actions>\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 () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button onClick={e => actions.increment(33)}>home increment</button>\n      <button onClick={e => sharedActions.increment(20)}>\n        shared increment\n      </button>\n      <button onClick={e => actions.get()}>fake request</button>\n      <button onClick={e => actions.openLight()}>fake nested call</button>\n    </div>\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 >= 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> Hint: The state returned should only be used as readonly\n\n```jsx\nimport { useStore, getState } from '../model/index'\n\nconst BasicHook = () => {\n  const [state, actions] = useStore('Counter')\n  useEffect(() => {\n    console.log('some mounted actions from BasicHooks')\n    return () =>\n      console.log(\n        `Basic Hooks unmounted, current Counter state: ${JSON.stringify(\n          getState('Counter')\n        )}`\n      )\n  }, [])\n  return (\n    <>\n      <div>state: {JSON.stringify(state)}</div>\n    </>\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: () => {\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 = () => {\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (params, { state: s }) => {\n      // return (state: typeof s) => { // TypeScript < 3.9\n      return state => {\n        state.counter += params || 1\n      }\n    },\n    decrease: params => s => {\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) => {\n      return state => {\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<details>\n<summary>Store: shared.ts</summary>\n<p>\n\n```ts\nconst initialState = {\n  counter: 0\n}\n\nconst model: ModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: (params, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    }\n  },\n  // Provide for SSR\n  asyncState: async context => {\n    await waitFor(4000)\n    return { counter: 500 }\n  },\n  state: initialState\n}\n\nexport default model\n```\n\n</p>\n</details>\n\n<details>\n<summary>Global Config: _app.tsx</summary>\n<p>\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) => {\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    <Container>\n      <Component {...pageProps} />\n    </Container>\n  )\n}\n\nMyApp.getInitialProps = async (context: NextAppContext) => {\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</p>\n</details>\n\n<details>\n<summary>Page: hooks/index.tsx</summary>\n<p>\n\n```tsx\nimport { useStore, getState } from '../index'\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button\n        onClick={e => {\n          actions.increment(33)\n        }}\n      >\n    </div>\n  )\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Single Page Config: benchmark.tsx</summary>\n<p>\n\n```tsx\n// ...\nBenchmark.getInitialProps = async () => {\n  return await getInitialState({ modelName: 'Todo' }, { isServer: true })\n}\n```\n</p>\n</details>\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<{}> = async (context, restMiddlewares) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: any) => 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) => async (params: any) => {\n  const context: Context = {\n    modelName,\n    setState,\n    actionName: action.name,\n    next: () => {},\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<\n  { name: string }, // State Type\n  { ext: undefined }, // ActionParamsType\n  { name: string } // ExtContextType\n> = {\n  actions: {\n    // { state, action } => { state, action, [name] }\n    ext: (_, { name }) => {\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 => '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      <Provider>\n        <Counter />\n      </Provider>\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<details>\n<summary>Javascript decorator version</summary>\n<p>\n\n```jsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\n\nconst mapProps = ({ light, counter }) => ({\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      <>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(5)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n      </>\n    )\n  }\n}\n```\n\n</p>\n</details>\n\n<details>\n<summary>TypeScript Version</summary>\n<p>\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) => ({\n  lightStatus: light ? 'open' : 'close',\n  counter,\n  response\n})\n\ntype RType = ReturnType<typeof mapProps>\n\nclass TSCounter extends PureComponent<\n  { state: RType } & { actions: ActionType }\n> {\n  render() {\n    const { state, actions } = this.props\n    return (\n      <>\n        <div>TS Counter</div>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(3)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n        <button onClick={e => actions.get()}>Get Response</button>\n        <div>message: {JSON.stringify(state.response)}</div>\n      </>\n    )\n  }\n}\n\nexport default connect(\n  'Home',\n  mapProps\n)(TSCounter)\n```\n</p>\n</details>\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<\n  CounterState,\n  CounterActionParams\n> = {\n  actions: {\n    increment: (params) => {\n      return (state) => {\n        state.count += params\n      }\n    }\n  },\n  state: { count: 0 }\n}\n\n// v4.1.x\nconst Counter = createStore(() => {\n  const [state, setState] = useModel({ count: 0 })\n  const actions = {\n    increment: (params) => {\n      setState((state) => {\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 = () => {\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<State, ActionParams> = {\n  state: {\n    counter: 1\n    enable: false\n  },\n  actions: {\n    add: (payload) => state => {\n      state.counter += payload\n    },\n    switch: () => state => {\n      state.enable = !state.enable\n    }\n  }\n}\n```\n\n```ts\nconst Component = () => {\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 => 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 }) => ['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) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: Error) => 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) => {\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      <div className=\"App\">\n        <Counter />\n        <Counter />\n        <Counter />\n      </div>\n    )\n  }\n}\n```\n\n<details>\n<summary>Counter model</summary>\n<p>\n\n```ts\ninterface State {\n  count: number\n}\n\ninterface ActionParams {\n  increment: number\n}\n\nconst model: ModelType<State, ActionParams> = {\n  state: {\n    count: 0\n  },\n  actions: {\n    increment: payload => {\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 => (state.count += payload)\n      return state => {\n        state.count += payload\n      }\n    }\n  }\n}\n\n```\n\n</p>\n</details>\n\n<details>\n<summary>Counter.tsx</summary>\n<p>\n\n```tsx\n\nconst Counter = () => {\n  const [{ useStore }] = useState(() => Model(model))\n  const [state, actions] = useStore()\n  return (\n    <div>\n      <div>{state.count}</div>\n      <button onClick={() => actions.increment(3)}>Increment</button>\n    </div>\n  )\n}\n\nexport default Counter\n```\n\n</p>\n</details>\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<ExpensiveState, ExpensiveActionParams> = {\n  state: {\n    moduleList: []\n  },\n  actions: {\n    setPreFreezedDataset: () => {\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 => {\n    // Not allowed\n    // return state => (state.count += payload)\n    return state => {\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) => {\n  await timeout(1000, {})\n  context.next(restMiddlewares)\n}\n\nconst nextCounterModel: ModelType<CounterState, NextCounterActionParams> = {\n  actions: {\n    add: num => {\n      return state => {\n        state.count += num\n      }\n    },\n    increment: async (num, { actions }) => {\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","readmeFilename":"README.md","gitHead":"4577f992a7bc593120a6c11c5edb16ddaf14b70e","_id":"react-model@4.2.1-rc.0","_nodeVersion":"12.22.1","_npmVersion":"6.14.12","dist":{"integrity":"sha512-d8E/Nsg5JvcbQum6+NIxRTyPw/mLZNw+q/zjo5IZgRAE/IXo00EnOMPdl5iq/4d9yfQECyDPnlI76eTIy+Kh0g==","shasum":"3eb4ae53d4e301c9621acaf2a2a9569eb6ce1f31","tarball":"https://registry.npmjs.org/react-model/-/react-model-4.2.1-rc.0.tgz","fileCount":22,"unpackedSize":113498,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh3TXjCRA9TVsSAnZWagAALoIQAIe2boKqoj4azf5xSxlt\neIqWXwUrtUlEOtgtmYEgzk5KfAt3Ib5S4Ib1t4WGdvxIW/wOabl5+OQTbZWZ\ndka3saE9/IAngbXeg5PgENNn8SBfUVmnNkodLi2PXboXJ7d3zXkkqp92AJIA\nQHqIiZ0xnxXxBv+2DJbFZHsawHW7kh7J+0bF5JJ1lC+aMqOMyTPMzlDfXz9X\nDO2iBRmLHgdDqBEsqhYk5MZRhTOOuE9BMZcMt0ELx0POrethgw5/qLb703Ql\nJEcO0e9gN54yp0sOt7P/6Oca+Eiye193p0lZ2Q6sRSP7Jky4M4UAVLDGoR7R\nNVI3NVvGzRRrSR/nWD05OK7P0rMAwK3w6HGsXbzsfAA5+XRs91pn22iV63+u\nMzj0WrG87lpmQOFh+i46aAGetXD49AE1yvhChtmlAb61bkPCxGL1z/oxVsBi\niwOFqJrjeiNOTc1kL91lHlBy5WD5JcqSE3ZNpM+x4IDF54W+s1WdxAiubqKa\n+RcLtTh/7qEOLvY08OFwOXgFLURS9e87S5Opt1IeVqfEmuUDxv7+OVIEI0/T\nVTMShxc1eVyXnjHPo7CWgnmri2C3hwiggUbwVIdP/xzxCckIy95869Zz+eLr\n6PKrBL0M3WH8vsPFw1mp1WW+cRs66NehB/EusW+vtxn6wSLGacVqBf+4G877\nz2N2\r\n=olRb\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQD3yEmofbR6udGXMhqlCNFPAO4N6E3mvqK/bbTL0einnQIgOjN4wcoANEHDKZ/CNh/62bmmlPYFPOBUqAG2Tys4a6U="}]},"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"maintainers":[{"name":"leecade","email":"leecade@163.com"},{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_4.2.1-rc.0_1641887203542_0.7027005186034965"},"_hasShrinkwrap":false},"4.2.1":{"name":"react-model","version":"4.2.1","description":"The State management library for React","main":"./dist/react-model.js","module":"./dist/react-model.esm.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build:prod":"microbundle --define process.env.NODE_ENV=production --sourcemap false --jsx React.createElement --output dist --tsconfig ./tsconfig.json","build:dev":"microbundle --define process.env.NODE_ENV=development --sourcemap true --jsx React.createElement --output dist --tsconfig ./tsconfig.json","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":">=8.0.1"},"peerDependencies":{"react":">=16.3.0","react-dom":">=16.3.0","typescript":">=3.9.0"},"devDependencies":{"@commitlint/cli":"^8.1.0","@commitlint/config-conventional":"^8.1.0","@testing-library/react":"^10.0.1","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/faker":"^5.5.3","@types/jest":"^25.1.0","@types/node":"^14.0.0","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","faker":"^5.5.3","husky":"^4.0.2","jest":"^24.1.0","microbundle":"^0.12.3","prettier":"^2.0.0","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^8.0.0","remark-lint":"^7.0.0","remark-preset-lint-recommended":"^4.0.0","ts-jest":"^26.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"browserslist":["edge 17","firefox 70","chrome 48","safari 12.1","android 4.0","samsung 9.2"],"gitHead":"f04662b06fbf18e7d1f06ed0cd50f82af62b6e61","_id":"react-model@4.2.1","_nodeVersion":"12.22.1","_npmVersion":"6.14.12","dist":{"integrity":"sha512-vFHuIJW+AtSAHQSP9sWDf0B+NimBzvAiAhnJ+umZ/ipOk8hK0FQsRn0vglWeeZhdrfYF8Y0Xy6IDVG8WhhDUxg==","shasum":"e75c885a1bc9eed6c1551ad114b5f06dc9190038","tarball":"https://registry.npmjs.org/react-model/-/react-model-4.2.1.tgz","fileCount":22,"unpackedSize":113493,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh38MFCRA9TVsSAnZWagAAc2gQAI06CFEkqN7NSFZ6Lu7n\nA6YLTzo88XF5dnN9PwFmZDDI/X4Q8GsvZRu70EB5FHTHMjK1qZUg86ZvbI3L\nRLlbNuSwwPAqEhUcDX79QCgLTgfjrfzSCQSJf/h8zwadnlmbv6L/BfZFXgWB\nAaTnce99VhgVw0o5R2FKhBtsWH1UNEjATj6A0sr5Vf5oB0GU6vm8PgFecCnG\nvLwSrmm79z6UIF45Fh/4kN6bhGtBTBgQH+jsHZ8SPuovuMGW2+VHA1AD4iso\nodwkl13W/SUxMG2/b+8V9v3qqDNvgMUtalUkQ4zc6zrsYpkkJ+7DYkE5JN97\nVSWiRFF1Wd0Y5ub0TxkZjHYbM4TbfMxxB8Ok9X2qyMJ3EzNGTJYcjLDdssZt\ny7pVdUsNbaP1xM2mQj8gBrYnC6avrukjsMsYeBNetJiS9rAeomwrxj2whcZy\n8duOAIm5whi8WM9Rz+4KT4qaZtHglF8MR4/p7xR1xIWiUZnY9aFT7wwKCBZV\nd34VTt44wtTX3TiGpdaF7aKGYGVMVcr58J47C/dbk65heFJF6pWC9KManZzh\n61Mub3q9iJTIQId6rJOGzBd22LDp94SBY1ftFazvcRjk01uPPaCGUP0sryIA\nn+lJ9q/SYzFydgqsy1KMu0bvWSYVeeOxHBsWKYdP6lntXeFUL9SSIxB0td6N\nVOSg\r\n=YjLP\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQD/Hff8qxrLbQChu/HOstVG9gKeMogNgoxzkGsEkelulwIhAN1luWwfXHENYiHnCjcdgVDJm/4yf7Wi+cFHudzrBWKP"}]},"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"maintainers":[{"name":"leecade","email":"leecade@163.com"},{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_4.2.1_1642054404961_0.8082420532707302"},"_hasShrinkwrap":false},"4.3.0-alpha.1":{"name":"react-model","version":"4.3.0-alpha.1","description":"The State management library for React","main":"./dist/react-model.js","module":"./dist/react-model.esm.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build:prod":"microbundle --define process.env.NODE_ENV=production --sourcemap false --jsx React.createElement --output dist --tsconfig ./tsconfig.json","build:dev":"microbundle --define process.env.NODE_ENV=development --sourcemap true --jsx React.createElement --output dist --tsconfig ./tsconfig.json","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":">=8.0.1"},"peerDependencies":{"react":">=16.3.0","react-dom":">=16.3.0","typescript":">=3.9.0"},"devDependencies":{"@commitlint/cli":"^8.1.0","@commitlint/config-conventional":"^8.1.0","@testing-library/react":"^10.0.1","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/faker":"^5.5.3","@types/jest":"^25.1.0","@types/node":"^14.0.0","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","faker":"^5.5.3","husky":"^4.0.2","jest":"^24.1.0","microbundle":"^0.12.3","prettier":"^2.0.0","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^8.0.0","remark-lint":"^7.0.0","remark-preset-lint-recommended":"^4.0.0","ts-jest":"^26.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"browserslist":["edge 17","firefox 70","chrome 48","safari 12.1","android 4.0","samsung 9.2"],"readme":"# react-model &middot; ![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) [![Build Status](https://travis-ci.org/byte-fe/react-model.svg?branch=master)](https://travis-ci.org/byte-fe/react-model) [![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 = () => {\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 = () => {\n  return <TodoList />\n}\n\nconst TodoList = () => {\n  const { items, setItems } = useStore()\n  return <div>\n    <Addon handler={setItems} />\n    {state.items.map((item, index) => (<Todo key={index} item={item} />))}\n  </div>\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(() => {\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 = () => {\n    setLocalCount(localCount + 1)\n  }\n  const inc = () => {\n    setCount(c => 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 = () => {\n  const { count, localCount, inc, incLocal } = useStore()\n  return <div>\n    <span>count: { count }</span>\n    <span>localCount: { localCount }</span>\n    <button onClick={inc}>inc</button>\n    <button onClick={incLocal}>incLocal</button>\n  </div>\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (payload, { state }) => {\n      return {\n        counter: state.counter + (payload || 1)\n      }\n    },\n    openLight: async (_, { state, actions }) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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<typeof Model.actions>\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 () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button onClick={e => actions.increment(33)}>home increment</button>\n      <button onClick={e => sharedActions.increment(20)}>\n        shared increment\n      </button>\n      <button onClick={e => actions.get()}>fake request</button>\n      <button onClick={e => actions.openLight()}>fake nested call</button>\n    </div>\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 >= 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> Hint: The state returned should only be used as readonly\n\n```jsx\nimport { useStore, getState } from '../model/index'\n\nconst BasicHook = () => {\n  const [state, actions] = useStore('Counter')\n  useEffect(() => {\n    console.log('some mounted actions from BasicHooks')\n    return () =>\n      console.log(\n        `Basic Hooks unmounted, current Counter state: ${JSON.stringify(\n          getState('Counter')\n        )}`\n      )\n  }, [])\n  return (\n    <>\n      <div>state: {JSON.stringify(state)}</div>\n    </>\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: () => {\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 = () => {\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (params, { state: s }) => {\n      // return (state: typeof s) => { // TypeScript < 3.9\n      return state => {\n        state.counter += params || 1\n      }\n    },\n    decrease: params => s => {\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) => {\n      return state => {\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<details>\n<summary>Store: shared.ts</summary>\n<p>\n\n```ts\nconst initialState = {\n  counter: 0\n}\n\nconst model: ModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: (params, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    }\n  },\n  // Provide for SSR\n  asyncState: async context => {\n    await waitFor(4000)\n    return { counter: 500 }\n  },\n  state: initialState\n}\n\nexport default model\n```\n\n</p>\n</details>\n\n<details>\n<summary>Global Config: _app.tsx</summary>\n<p>\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) => {\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    <Container>\n      <Component {...pageProps} />\n    </Container>\n  )\n}\n\nMyApp.getInitialProps = async (context: NextAppContext) => {\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</p>\n</details>\n\n<details>\n<summary>Page: hooks/index.tsx</summary>\n<p>\n\n```tsx\nimport { useStore, getState } from '../index'\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button\n        onClick={e => {\n          actions.increment(33)\n        }}\n      >\n    </div>\n  )\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Single Page Config: benchmark.tsx</summary>\n<p>\n\n```tsx\n// ...\nBenchmark.getInitialProps = async () => {\n  return await getInitialState({ modelName: 'Todo' }, { isServer: true })\n}\n```\n</p>\n</details>\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<{}> = async (context, restMiddlewares) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: any) => 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) => async (params: any) => {\n  const context: Context = {\n    modelName,\n    setState,\n    actionName: action.name,\n    next: () => {},\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<\n  { name: string }, // State Type\n  { ext: undefined }, // ActionParamsType\n  { name: string } // ExtContextType\n> = {\n  actions: {\n    // { state, action } => { state, action, [name] }\n    ext: (_, { name }) => {\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 => '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      <Provider>\n        <Counter />\n      </Provider>\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<details>\n<summary>Javascript decorator version</summary>\n<p>\n\n```jsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\n\nconst mapProps = ({ light, counter }) => ({\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      <>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(5)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n      </>\n    )\n  }\n}\n```\n\n</p>\n</details>\n\n<details>\n<summary>TypeScript Version</summary>\n<p>\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) => ({\n  lightStatus: light ? 'open' : 'close',\n  counter,\n  response\n})\n\ntype RType = ReturnType<typeof mapProps>\n\nclass TSCounter extends PureComponent<\n  { state: RType } & { actions: ActionType }\n> {\n  render() {\n    const { state, actions } = this.props\n    return (\n      <>\n        <div>TS Counter</div>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(3)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n        <button onClick={e => actions.get()}>Get Response</button>\n        <div>message: {JSON.stringify(state.response)}</div>\n      </>\n    )\n  }\n}\n\nexport default connect(\n  'Home',\n  mapProps\n)(TSCounter)\n```\n</p>\n</details>\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<\n  CounterState,\n  CounterActionParams\n> = {\n  actions: {\n    increment: (params) => {\n      return (state) => {\n        state.count += params\n      }\n    }\n  },\n  state: { count: 0 }\n}\n\n// v4.1.x\nconst Counter = createStore(() => {\n  const [state, setState] = useModel({ count: 0 })\n  const actions = {\n    increment: (params) => {\n      setState((state) => {\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 = () => {\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<State, ActionParams> = {\n  state: {\n    counter: 1\n    enable: false\n  },\n  actions: {\n    add: (payload) => state => {\n      state.counter += payload\n    },\n    switch: () => state => {\n      state.enable = !state.enable\n    }\n  }\n}\n```\n\n```ts\nconst Component = () => {\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 => 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 }) => ['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) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: Error) => 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) => {\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      <div className=\"App\">\n        <Counter />\n        <Counter />\n        <Counter />\n      </div>\n    )\n  }\n}\n```\n\n<details>\n<summary>Counter model</summary>\n<p>\n\n```ts\ninterface State {\n  count: number\n}\n\ninterface ActionParams {\n  increment: number\n}\n\nconst model: ModelType<State, ActionParams> = {\n  state: {\n    count: 0\n  },\n  actions: {\n    increment: payload => {\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 => (state.count += payload)\n      return state => {\n        state.count += payload\n      }\n    }\n  }\n}\n\n```\n\n</p>\n</details>\n\n<details>\n<summary>Counter.tsx</summary>\n<p>\n\n```tsx\n\nconst Counter = () => {\n  const [{ useStore }] = useState(() => Model(model))\n  const [state, actions] = useStore()\n  return (\n    <div>\n      <div>{state.count}</div>\n      <button onClick={() => actions.increment(3)}>Increment</button>\n    </div>\n  )\n}\n\nexport default Counter\n```\n\n</p>\n</details>\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<ExpensiveState, ExpensiveActionParams> = {\n  state: {\n    moduleList: []\n  },\n  actions: {\n    setPreFreezedDataset: () => {\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 => {\n    // Not allowed\n    // return state => (state.count += payload)\n    return state => {\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) => {\n  await timeout(1000, {})\n  context.next(restMiddlewares)\n}\n\nconst nextCounterModel: ModelType<CounterState, NextCounterActionParams> = {\n  actions: {\n    add: num => {\n      return state => {\n        state.count += num\n      }\n    },\n    increment: async (num, { actions }) => {\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","readmeFilename":"README.md","gitHead":"b36e69cace4db6ce7ac667297161c4c18ccb14e4","_id":"react-model@4.3.0-alpha.1","_nodeVersion":"16.14.0","_npmVersion":"8.3.1","dist":{"integrity":"sha512-dHN43B6qxOShEz+6IWg4iO5zuLd4vnGFl3vTeiXaLYeoEkdfQzxjywHiXsfyUJq097n+wKHTaNQLvi1+xf6TsQ==","shasum":"d51444a43de01ef7f1064f77d95efcc0214dd262","tarball":"https://registry.npmjs.org/react-model/-/react-model-4.3.0-alpha.1.tgz","fileCount":22,"unpackedSize":114243,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiElk4ACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmqNbBAAgkWLlyuhT2t5EKlbEvMWmfMrnL29BRxhwVcOmcQ1DP1PQzv9\r\nuboqkG7Mr/vLoNXrh2WLypoXaulPhEV9CJ4i3DCaaN1nJwFbmN4gy1h2UJP2\r\nFrOjeC9PiDiG2Ip1cGUtVNvnPMW+g0zkdwAk6DAavmVCc8n9VRU0kBRbBNNm\r\n77VbVhE4IYRoRMvz9t6O4LaZgjgtX5pgjAAqiUVoXptwQhY8VYOaPyu5SMKb\r\nXZt6dW5rCfXarUm1hdF2bmrfhm9Sb+3cBgZPMOsWcpGMoekd/xI5BT4CRUfc\r\nwUfFs6iyWsPmpIfsy8U6WQpLJF1q322eluE65xxoQEE06WfEjxHSIy6LcHyt\r\nMPGfufdIP6d2Up0BhzHPhRXcOAiOj84YCsNLlfhMPft1Tp+Thspt+gw7Xnr7\r\nUTcnpRUI1hiE0vGjgujm1ZjMefyO0IjG2+Ac0/T0QQ85rCEulIOCBN3j92a9\r\n9JEgMlqZEIgs5GcpxdLzEIU1hziTXP5MXL70K/Pv+X+vahprGoPWI63p+D4K\r\nySjkJQDxoeeDTXR0JHhggpAa58aFfznRGYcbCaw0AkzbDDtit+8L0rYlwGPi\r\nytVEVD+6Mn6EZ6Rw5iu0BJeN+14jzcYxiezc6jzYksBdCoY5PlpeB1NSvAmZ\r\ncewtKAotVTIreyIkIv/dEyv1asfRUKu8sDY=\r\n=FD9U\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQCZRes1bs0wsFISikownLLkfTv3gkCb8DQR1rdwjGtudAIhANUy53GZzUAghtF0/4j4LwuCiSieX7HmZ3v4P/8vZRkG"}]},"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"maintainers":[{"name":"leecade","email":"leecade@163.com"},{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_4.3.0-alpha.1_1645369656605_0.4014524525365428"},"_hasShrinkwrap":false},"4.3.0-rc.0":{"name":"react-model","version":"4.3.0-rc.0","description":"The State management library for React","main":"./dist/react-model.js","module":"./dist/react-model.esm.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build:prod":"microbundle --define process.env.NODE_ENV=production --sourcemap false --jsx React.createElement --output dist --tsconfig ./tsconfig.json","build:dev":"microbundle --define process.env.NODE_ENV=development --sourcemap true --jsx React.createElement --output dist --tsconfig ./tsconfig.json","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":">=8.0.1"},"peerDependencies":{"react":">=16.3.0","react-dom":">=16.3.0","typescript":">=3.9.0"},"devDependencies":{"@commitlint/cli":"^8.1.0","@commitlint/config-conventional":"^8.1.0","@testing-library/react":"^10.0.1","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/faker":"^5.5.3","@types/jest":"^25.1.0","@types/node":"^14.0.0","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","faker":"^5.5.3","husky":"^4.0.2","jest":"^24.1.0","microbundle":"^0.12.3","prettier":"^2.0.0","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^8.0.0","remark-lint":"^7.0.0","remark-preset-lint-recommended":"^4.0.0","ts-jest":"^26.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"browserslist":["edge 17","firefox 70","chrome 48","safari 12.1","android 4.0","samsung 9.2"],"readme":"# react-model &middot; ![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) [![Build Status](https://travis-ci.org/byte-fe/react-model.svg?branch=master)](https://travis-ci.org/byte-fe/react-model) [![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 = () => {\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 = () => {\n  return <TodoList />\n}\n\nconst TodoList = () => {\n  const { items, setItems } = useStore()\n  return <div>\n    <Addon handler={setItems} />\n    {state.items.map((item, index) => (<Todo key={index} item={item} />))}\n  </div>\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(() => {\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 = () => {\n    setLocalCount(localCount + 1)\n  }\n  const inc = () => {\n    setCount(c => 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 = () => {\n  const { count, localCount, inc, incLocal } = useStore()\n  return <div>\n    <span>count: { count }</span>\n    <span>localCount: { localCount }</span>\n    <button onClick={inc}>inc</button>\n    <button onClick={incLocal}>incLocal</button>\n  </div>\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (payload, { state }) => {\n      return {\n        counter: state.counter + (payload || 1)\n      }\n    },\n    openLight: async (_, { state, actions }) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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<typeof Model.actions>\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 () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button onClick={e => actions.increment(33)}>home increment</button>\n      <button onClick={e => sharedActions.increment(20)}>\n        shared increment\n      </button>\n      <button onClick={e => actions.get()}>fake request</button>\n      <button onClick={e => actions.openLight()}>fake nested call</button>\n    </div>\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 >= 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> Hint: The state returned should only be used as readonly\n\n```jsx\nimport { useStore, getState } from '../model/index'\n\nconst BasicHook = () => {\n  const [state, actions] = useStore('Counter')\n  useEffect(() => {\n    console.log('some mounted actions from BasicHooks')\n    return () =>\n      console.log(\n        `Basic Hooks unmounted, current Counter state: ${JSON.stringify(\n          getState('Counter')\n        )}`\n      )\n  }, [])\n  return (\n    <>\n      <div>state: {JSON.stringify(state)}</div>\n    </>\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: () => {\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 = () => {\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (params, { state: s }) => {\n      // return (state: typeof s) => { // TypeScript < 3.9\n      return state => {\n        state.counter += params || 1\n      }\n    },\n    decrease: params => s => {\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) => {\n      return state => {\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<details>\n<summary>Store: shared.ts</summary>\n<p>\n\n```ts\nconst initialState = {\n  counter: 0\n}\n\nconst model: ModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: (params, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    }\n  },\n  // Provide for SSR\n  asyncState: async context => {\n    await waitFor(4000)\n    return { counter: 500 }\n  },\n  state: initialState\n}\n\nexport default model\n```\n\n</p>\n</details>\n\n<details>\n<summary>Global Config: _app.tsx</summary>\n<p>\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) => {\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    <Container>\n      <Component {...pageProps} />\n    </Container>\n  )\n}\n\nMyApp.getInitialProps = async (context: NextAppContext) => {\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</p>\n</details>\n\n<details>\n<summary>Page: hooks/index.tsx</summary>\n<p>\n\n```tsx\nimport { useStore, getState } from '../index'\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button\n        onClick={e => {\n          actions.increment(33)\n        }}\n      >\n    </div>\n  )\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Single Page Config: benchmark.tsx</summary>\n<p>\n\n```tsx\n// ...\nBenchmark.getInitialProps = async () => {\n  return await getInitialState({ modelName: 'Todo' }, { isServer: true })\n}\n```\n</p>\n</details>\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<{}> = async (context, restMiddlewares) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: any) => 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) => async (params: any) => {\n  const context: Context = {\n    modelName,\n    setState,\n    actionName: action.name,\n    next: () => {},\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<\n  { name: string }, // State Type\n  { ext: undefined }, // ActionParamsType\n  { name: string } // ExtContextType\n> = {\n  actions: {\n    // { state, action } => { state, action, [name] }\n    ext: (_, { name }) => {\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 => '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      <Provider>\n        <Counter />\n      </Provider>\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<details>\n<summary>Javascript decorator version</summary>\n<p>\n\n```jsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\n\nconst mapProps = ({ light, counter }) => ({\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      <>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(5)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n      </>\n    )\n  }\n}\n```\n\n</p>\n</details>\n\n<details>\n<summary>TypeScript Version</summary>\n<p>\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) => ({\n  lightStatus: light ? 'open' : 'close',\n  counter,\n  response\n})\n\ntype RType = ReturnType<typeof mapProps>\n\nclass TSCounter extends PureComponent<\n  { state: RType } & { actions: ActionType }\n> {\n  render() {\n    const { state, actions } = this.props\n    return (\n      <>\n        <div>TS Counter</div>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(3)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n        <button onClick={e => actions.get()}>Get Response</button>\n        <div>message: {JSON.stringify(state.response)}</div>\n      </>\n    )\n  }\n}\n\nexport default connect(\n  'Home',\n  mapProps\n)(TSCounter)\n```\n</p>\n</details>\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<\n  CounterState,\n  CounterActionParams\n> = {\n  actions: {\n    increment: (params) => {\n      return (state) => {\n        state.count += params\n      }\n    }\n  },\n  state: { count: 0 }\n}\n\n// v4.1.x\nconst Counter = createStore(() => {\n  const [state, setState] = useModel({ count: 0 })\n  const actions = {\n    increment: (params) => {\n      setState((state) => {\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 = () => {\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<State, ActionParams> = {\n  state: {\n    counter: 1\n    enable: false\n  },\n  actions: {\n    add: (payload) => state => {\n      state.counter += payload\n    },\n    switch: () => state => {\n      state.enable = !state.enable\n    }\n  }\n}\n```\n\n```ts\nconst Component = () => {\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 => 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 }) => ['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) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: Error) => 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) => {\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      <div className=\"App\">\n        <Counter />\n        <Counter />\n        <Counter />\n      </div>\n    )\n  }\n}\n```\n\n<details>\n<summary>Counter model</summary>\n<p>\n\n```ts\ninterface State {\n  count: number\n}\n\ninterface ActionParams {\n  increment: number\n}\n\nconst model: ModelType<State, ActionParams> = {\n  state: {\n    count: 0\n  },\n  actions: {\n    increment: payload => {\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 => (state.count += payload)\n      return state => {\n        state.count += payload\n      }\n    }\n  }\n}\n\n```\n\n</p>\n</details>\n\n<details>\n<summary>Counter.tsx</summary>\n<p>\n\n```tsx\n\nconst Counter = () => {\n  const [{ useStore }] = useState(() => Model(model))\n  const [state, actions] = useStore()\n  return (\n    <div>\n      <div>{state.count}</div>\n      <button onClick={() => actions.increment(3)}>Increment</button>\n    </div>\n  )\n}\n\nexport default Counter\n```\n\n</p>\n</details>\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<ExpensiveState, ExpensiveActionParams> = {\n  state: {\n    moduleList: []\n  },\n  actions: {\n    setPreFreezedDataset: () => {\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 => {\n    // Not allowed\n    // return state => (state.count += payload)\n    return state => {\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) => {\n  await timeout(1000, {})\n  context.next(restMiddlewares)\n}\n\nconst nextCounterModel: ModelType<CounterState, NextCounterActionParams> = {\n  actions: {\n    add: num => {\n      return state => {\n        state.count += num\n      }\n    },\n    increment: async (num, { actions }) => {\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","readmeFilename":"README.md","gitHead":"19e9ea08c15187bc620e2e758375fbef1f985459","_id":"react-model@4.3.0-rc.0","_nodeVersion":"16.14.0","_npmVersion":"8.3.1","dist":{"integrity":"sha512-WWsRH+1dFYheODI8oFGiUtjSBBSzh/3t396/O2f1cJtpB5Q5ch6HXD4E2k5AecuFnRDa/s/XrrVjmUZFvjXIhA==","shasum":"225cd52756522321e8700c20574c55fc0ef4fa27","tarball":"https://registry.npmjs.org/react-model/-/react-model-4.3.0-rc.0.tgz","fileCount":22,"unpackedSize":114135,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiHdJGACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmqF2g/9F/yAzAMTJJSlLyayZURS3JiJinhlaMpXVFtAB9kseKARjAi5\r\nRNrUt3DFnpGEJz/1R5CWBbeEVnwTQ0i3VNSlATK73keOfW+8g3l/y8O/9e3j\r\n5ar9q6VbpKmG8qkVfa3Lvi+Oi61tXiCJciOyHtMhq7J2rvS/8nImU+2ovMpq\r\n+t1C1km/G6KyK/1s95hkCahFmNIm0sMkgdnBl/CDw1iFDIZ39STdXuLPvovZ\r\n79Hjs5wh3gK1v6wR3xEqdoqJXOd3M1t4c7xF35QShsU7EQjPWPcv+FvqmbAj\r\n/QJddpzAKSJmFcK3SFlVsU7jEYEnTXYbsM/P3OjQHCTujmBCjgsJXLTpF2Xz\r\nQJyWR81Dau6lOqW+Mt7M9Bxx4OJTpYFm4Ec1D5KkX76wSK6IrA7P3N9dY0Yu\r\n6wtR7rlPH7LHyOo8FO3I1zxgs/avRATEGRel1rlIUf686+p1rhm+eCne/Lh8\r\nDElPFeNBkekwnF2nyRt/CUpH7w9pQN8EizFlhb9BcUZFmZKJ/VwbiqHLDeWT\r\nTNM0RPLkGJ5rpUWeAiII9e9ip+iE8wozWTLxYAF5KMY5nW/UP8CW2zvn6I4d\r\nJwaYjyCe/Y0u7OXgdJ1vyITUgL44k47MNFhzmsqxa4qC6Hz+jvQcUnIWMcR2\r\noBIHMLRETp6K4QOdCQeBpNTOxO/NPjEWz6E=\r\n=awdl\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDfI792urY1Q0ktTMuUUExEJpMdwWhtKmoRGLJFuEIjTAIgSGC9Vfv/BEKQP8U4YVAr7NL9P4I8YenDyjtV7QfG/IM="}]},"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"maintainers":[{"name":"leecade","email":"leecade@163.com"},{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_4.3.0-rc.0_1646121542400_0.18932831469167022"},"_hasShrinkwrap":false},"4.3.0-alpha.2":{"name":"react-model","version":"4.3.0-alpha.2","description":"The State management library for React","main":"./dist/react-model.js","module":"./dist/react-model.esm.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build:prod":"microbundle --define process.env.NODE_ENV=production --sourcemap false --jsx React.createElement --output dist --tsconfig ./tsconfig.json","build:dev":"microbundle --define process.env.NODE_ENV=development --sourcemap true --jsx React.createElement --output dist --tsconfig ./tsconfig.json","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":">=8.0.1"},"peerDependencies":{"react":">=16.3.0","react-dom":">=16.3.0","typescript":">=3.9.0"},"devDependencies":{"@commitlint/cli":"^16.3.0","@commitlint/config-conventional":"^16.2.4","@testing-library/react":"^10.0.1","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/faker":"^5.5.3","@types/jest":"^25.1.0","@types/node":"^14.0.0","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","faker":"^5.5.3","husky":"^4.0.2","jest":"^24.1.0","microbundle":"^0.12.3","prettier":"^2.0.0","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^10.0.1","remark-lint":"^9.1.1","remark-preset-lint-recommended":"^6.1.2","ts-jest":"^26.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"resolutions":{"minimist":">=1.2.6"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"browserslist":["edge 17","firefox 70","chrome 48","safari 12.1","android 4.0","samsung 9.2"],"readme":"# react-model &middot; ![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 = () => {\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 = () => {\n  return <TodoList />\n}\n\nconst TodoList = () => {\n  const { items, setItems } = useStore()\n  return <div>\n    <Addon handler={setItems} />\n    {state.items.map((item, index) => (<Todo key={index} item={item} />))}\n  </div>\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(() => {\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 = () => {\n    setLocalCount(localCount + 1)\n  }\n  const inc = () => {\n    setCount(c => 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 = () => {\n  const { count, localCount, inc, incLocal } = useStore()\n  return <div>\n    <span>count: { count }</span>\n    <span>localCount: { localCount }</span>\n    <button onClick={inc}>inc</button>\n    <button onClick={incLocal}>incLocal</button>\n  </div>\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (payload, { state }) => {\n      return {\n        counter: state.counter + (payload || 1)\n      }\n    },\n    openLight: async (_, { state, actions }) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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<typeof Model.actions>\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 () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button onClick={e => actions.increment(33)}>home increment</button>\n      <button onClick={e => sharedActions.increment(20)}>\n        shared increment\n      </button>\n      <button onClick={e => actions.get()}>fake request</button>\n      <button onClick={e => actions.openLight()}>fake nested call</button>\n    </div>\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 >= 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> Hint: The state returned should only be used as readonly\n\n```jsx\nimport { useStore, getState } from '../model/index'\n\nconst BasicHook = () => {\n  const [state, actions] = useStore('Counter')\n  useEffect(() => {\n    console.log('some mounted actions from BasicHooks')\n    return () =>\n      console.log(\n        `Basic Hooks unmounted, current Counter state: ${JSON.stringify(\n          getState('Counter')\n        )}`\n      )\n  }, [])\n  return (\n    <>\n      <div>state: {JSON.stringify(state)}</div>\n    </>\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: () => {\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 = () => {\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (params, { state: s }) => {\n      // return (state: typeof s) => { // TypeScript < 3.9\n      return state => {\n        state.counter += params || 1\n      }\n    },\n    decrease: params => s => {\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) => {\n      return state => {\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<details>\n<summary>Store: shared.ts</summary>\n<p>\n\n```ts\nconst initialState = {\n  counter: 0\n}\n\nconst model: ModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: (params, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    }\n  },\n  // Provide for SSR\n  asyncState: async context => {\n    await waitFor(4000)\n    return { counter: 500 }\n  },\n  state: initialState\n}\n\nexport default model\n```\n\n</p>\n</details>\n\n<details>\n<summary>Global Config: _app.tsx</summary>\n<p>\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) => {\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    <Container>\n      <Component {...pageProps} />\n    </Container>\n  )\n}\n\nMyApp.getInitialProps = async (context: NextAppContext) => {\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</p>\n</details>\n\n<details>\n<summary>Page: hooks/index.tsx</summary>\n<p>\n\n```tsx\nimport { useStore, getState } from '../index'\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button\n        onClick={e => {\n          actions.increment(33)\n        }}\n      >\n    </div>\n  )\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Single Page Config: benchmark.tsx</summary>\n<p>\n\n```tsx\n// ...\nBenchmark.getInitialProps = async () => {\n  return await getInitialState({ modelName: 'Todo' }, { isServer: true })\n}\n```\n</p>\n</details>\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<{}> = async (context, restMiddlewares) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: any) => 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) => async (params: any) => {\n  const context: Context = {\n    modelName,\n    setState,\n    actionName: action.name,\n    next: () => {},\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<\n  { name: string }, // State Type\n  { ext: undefined }, // ActionParamsType\n  { name: string } // ExtContextType\n> = {\n  actions: {\n    // { state, action } => { state, action, [name] }\n    ext: (_, { name }) => {\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 => '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      <Provider>\n        <Counter />\n      </Provider>\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<details>\n<summary>Javascript decorator version</summary>\n<p>\n\n```jsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\n\nconst mapProps = ({ light, counter }) => ({\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      <>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(5)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n      </>\n    )\n  }\n}\n```\n\n</p>\n</details>\n\n<details>\n<summary>TypeScript Version</summary>\n<p>\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) => ({\n  lightStatus: light ? 'open' : 'close',\n  counter,\n  response\n})\n\ntype RType = ReturnType<typeof mapProps>\n\nclass TSCounter extends PureComponent<\n  { state: RType } & { actions: ActionType }\n> {\n  render() {\n    const { state, actions } = this.props\n    return (\n      <>\n        <div>TS Counter</div>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(3)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n        <button onClick={e => actions.get()}>Get Response</button>\n        <div>message: {JSON.stringify(state.response)}</div>\n      </>\n    )\n  }\n}\n\nexport default connect(\n  'Home',\n  mapProps\n)(TSCounter)\n```\n</p>\n</details>\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<\n  CounterState,\n  CounterActionParams\n> = {\n  actions: {\n    increment: (params) => {\n      return (state) => {\n        state.count += params\n      }\n    }\n  },\n  state: { count: 0 }\n}\n\n// v4.1.x\nconst Counter = createStore(() => {\n  const [state, setState] = useModel({ count: 0 })\n  const actions = {\n    increment: (params) => {\n      setState((state) => {\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 = () => {\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<State, ActionParams> = {\n  state: {\n    counter: 1\n    enable: false\n  },\n  actions: {\n    add: (payload) => state => {\n      state.counter += payload\n    },\n    switch: () => state => {\n      state.enable = !state.enable\n    }\n  }\n}\n```\n\n```ts\nconst Component = () => {\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 => 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 }) => ['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) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: Error) => 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) => {\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      <div className=\"App\">\n        <Counter />\n        <Counter />\n        <Counter />\n      </div>\n    )\n  }\n}\n```\n\n<details>\n<summary>Counter model</summary>\n<p>\n\n```ts\ninterface State {\n  count: number\n}\n\ninterface ActionParams {\n  increment: number\n}\n\nconst model: ModelType<State, ActionParams> = {\n  state: {\n    count: 0\n  },\n  actions: {\n    increment: payload => {\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 => (state.count += payload)\n      return state => {\n        state.count += payload\n      }\n    }\n  }\n}\n\n```\n\n</p>\n</details>\n\n<details>\n<summary>Counter.tsx</summary>\n<p>\n\n```tsx\n\nconst Counter = () => {\n  const [{ useStore }] = useState(() => Model(model))\n  const [state, actions] = useStore()\n  return (\n    <div>\n      <div>{state.count}</div>\n      <button onClick={() => actions.increment(3)}>Increment</button>\n    </div>\n  )\n}\n\nexport default Counter\n```\n\n</p>\n</details>\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<ExpensiveState, ExpensiveActionParams> = {\n  state: {\n    moduleList: []\n  },\n  actions: {\n    setPreFreezedDataset: () => {\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 => {\n    // Not allowed\n    // return state => (state.count += payload)\n    return state => {\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) => {\n  await timeout(1000, {})\n  context.next(restMiddlewares)\n}\n\nconst nextCounterModel: ModelType<CounterState, NextCounterActionParams> = {\n  actions: {\n    add: num => {\n      return state => {\n        state.count += num\n      }\n    },\n    increment: async (num, { actions }) => {\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","readmeFilename":"README.md","gitHead":"a3605a15cd5b8b1688b146321615143eeeeddcb4","_id":"react-model@4.3.0-alpha.2","_nodeVersion":"16.16.0","_npmVersion":"8.11.0","dist":{"integrity":"sha512-oC7ecFQwsRdvI5rqyfYlqMg8bbV9r1R62T8L+ck5NKg2SixAQfkNvi6zbpEJK994EGmH4De5A8CeC0c5JaaCEQ==","shasum":"4cd7dde639519375fdea9637d5ed2761b5f00aa2","tarball":"https://registry.npmjs.org/react-model/-/react-model-4.3.0-alpha.2.tgz","fileCount":22,"unpackedSize":118283,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCHuq2iEc0mcwRlsxjg1500plkB3QtCtLabKxYPYGmfqgIgTcsHH3DGOnE4qrLeHSD8KKWTcMMzHlfRar8E+VqXtdk="}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJjIeZ5ACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmq0YxAAn36htimEOqZqFn9F7KUzf38tHMNHcUSVfx0O2OyBibLVoirz\r\nHxgOS8rluFB1e6qvP/pnhBSK8cwczaqfjrHlUnXz5/nynwVUIOdiQpG2H0BY\r\nta1s7wclli3pLfUHhGlu3ilZUTw8XNjOsX9u9PzHx5JIphG509P8dMRvbaFD\r\nVv8WDHzQUPFx1uyChJ33GZpMbXGbz3GpYwIbexRQ+aofHtZcW/rYmP2suHMz\r\nhd+tKr3vvTszJi9HEJ61I34+owSgeKxIC/q60qLaT764do5RWholMBI1rS4l\r\nJpFsBQYRW1hIl6VLZ2yHNDREWtkmHYzVf9/i6QHxve6SSpbAjyiZBHQspO5H\r\nvNxPlQY7Xm7ofCRvNrPu8XkxmjqOQJSqoBoDNipH7CR1WXVTzuMB9MPvb/l1\r\nkWZg5/+3Ts4oiSvgLYeQaQZQkJZvi7PLXpGPSekXmqNqIsyFEJx0Mr4i/cXj\r\nOlDcy+OEb//Bc/Y2njIvihrKR5crN2WRRBYz67fcnXdirvEMYtYEl5prFH0U\r\ngrf2vZ7yN4mHRb/qtEnPoCqSMg8C1cvhUEA9jJRedx/T9DzI1wUxicKgaDCJ\r\n27dSc3vFBRO/mVGKIJGagYVsevz3d3PmMeb+PqqbETdP+7pgcqWoWJ7M4d1N\r\nHhMpToty9zkVm174bNrjg+UXswpJA/nL+9s=\r\n=yc7K\r\n-----END PGP SIGNATURE-----\r\n"},"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"maintainers":[{"name":"leecade","email":"leecade@163.com"},{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_4.3.0-alpha.2_1663166073497_0.6397390122597146"},"_hasShrinkwrap":false},"4.3.0-alpha.3":{"name":"react-model","version":"4.3.0-alpha.3","description":"The State management library for React","main":"./dist/react-model.js","module":"./dist/react-model.esm.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build:prod":"microbundle --define process.env.NODE_ENV=production --sourcemap false --jsx React.createElement --output dist --tsconfig ./tsconfig.json","build:dev":"microbundle --define process.env.NODE_ENV=development --sourcemap true --jsx React.createElement --output dist --tsconfig ./tsconfig.json","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":">=8.0.1"},"peerDependencies":{"react":">=16.3.0","react-dom":">=16.3.0","typescript":">=3.9.0"},"devDependencies":{"@commitlint/cli":"^16.3.0","@commitlint/config-conventional":"^16.2.4","@testing-library/react":"^10.0.1","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/faker":"^5.5.3","@types/jest":"^25.1.0","@types/node":"^14.0.0","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","faker":"^5.5.3","husky":"^4.0.2","jest":"^24.1.0","microbundle":"^0.12.3","prettier":"^2.0.0","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^10.0.1","remark-lint":"^9.1.1","remark-preset-lint-recommended":"^6.1.2","ts-jest":"^26.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"resolutions":{"minimist":">=1.2.6"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"browserslist":["edge 17","firefox 70","chrome 48","safari 12.1","android 4.0","samsung 9.2"],"readme":"# react-model &middot; ![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 = () => {\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 = () => {\n  return <TodoList />\n}\n\nconst TodoList = () => {\n  const { items, setItems } = useStore()\n  return <div>\n    <Addon handler={setItems} />\n    {state.items.map((item, index) => (<Todo key={index} item={item} />))}\n  </div>\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(() => {\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 = () => {\n    setLocalCount(localCount + 1)\n  }\n  const inc = () => {\n    setCount(c => 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 = () => {\n  const { count, localCount, inc, incLocal } = useStore()\n  return <div>\n    <span>count: { count }</span>\n    <span>localCount: { localCount }</span>\n    <button onClick={inc}>inc</button>\n    <button onClick={incLocal}>incLocal</button>\n  </div>\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (payload, { state }) => {\n      return {\n        counter: state.counter + (payload || 1)\n      }\n    },\n    openLight: async (_, { state, actions }) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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<typeof Model.actions>\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 () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button onClick={e => actions.increment(33)}>home increment</button>\n      <button onClick={e => sharedActions.increment(20)}>\n        shared increment\n      </button>\n      <button onClick={e => actions.get()}>fake request</button>\n      <button onClick={e => actions.openLight()}>fake nested call</button>\n    </div>\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 >= 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> Hint: The state returned should only be used as readonly\n\n```jsx\nimport { useStore, getState } from '../model/index'\n\nconst BasicHook = () => {\n  const [state, actions] = useStore('Counter')\n  useEffect(() => {\n    console.log('some mounted actions from BasicHooks')\n    return () =>\n      console.log(\n        `Basic Hooks unmounted, current Counter state: ${JSON.stringify(\n          getState('Counter')\n        )}`\n      )\n  }, [])\n  return (\n    <>\n      <div>state: {JSON.stringify(state)}</div>\n    </>\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: () => {\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 = () => {\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (params, { state: s }) => {\n      // return (state: typeof s) => { // TypeScript < 3.9\n      return state => {\n        state.counter += params || 1\n      }\n    },\n    decrease: params => s => {\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) => {\n      return state => {\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<details>\n<summary>Store: shared.ts</summary>\n<p>\n\n```ts\nconst initialState = {\n  counter: 0\n}\n\nconst model: ModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: (params, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    }\n  },\n  // Provide for SSR\n  asyncState: async context => {\n    await waitFor(4000)\n    return { counter: 500 }\n  },\n  state: initialState\n}\n\nexport default model\n```\n\n</p>\n</details>\n\n<details>\n<summary>Global Config: _app.tsx</summary>\n<p>\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) => {\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    <Container>\n      <Component {...pageProps} />\n    </Container>\n  )\n}\n\nMyApp.getInitialProps = async (context: NextAppContext) => {\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</p>\n</details>\n\n<details>\n<summary>Page: hooks/index.tsx</summary>\n<p>\n\n```tsx\nimport { useStore, getState } from '../index'\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button\n        onClick={e => {\n          actions.increment(33)\n        }}\n      >\n    </div>\n  )\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Single Page Config: benchmark.tsx</summary>\n<p>\n\n```tsx\n// ...\nBenchmark.getInitialProps = async () => {\n  return await getInitialState({ modelName: 'Todo' }, { isServer: true })\n}\n```\n</p>\n</details>\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<{}> = async (context, restMiddlewares) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: any) => 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) => async (params: any) => {\n  const context: Context = {\n    modelName,\n    setState,\n    actionName: action.name,\n    next: () => {},\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<\n  { name: string }, // State Type\n  { ext: undefined }, // ActionParamsType\n  { name: string } // ExtContextType\n> = {\n  actions: {\n    // { state, action } => { state, action, [name] }\n    ext: (_, { name }) => {\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 => '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      <Provider>\n        <Counter />\n      </Provider>\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<details>\n<summary>Javascript decorator version</summary>\n<p>\n\n```jsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\n\nconst mapProps = ({ light, counter }) => ({\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      <>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(5)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n      </>\n    )\n  }\n}\n```\n\n</p>\n</details>\n\n<details>\n<summary>TypeScript Version</summary>\n<p>\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) => ({\n  lightStatus: light ? 'open' : 'close',\n  counter,\n  response\n})\n\ntype RType = ReturnType<typeof mapProps>\n\nclass TSCounter extends PureComponent<\n  { state: RType } & { actions: ActionType }\n> {\n  render() {\n    const { state, actions } = this.props\n    return (\n      <>\n        <div>TS Counter</div>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(3)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n        <button onClick={e => actions.get()}>Get Response</button>\n        <div>message: {JSON.stringify(state.response)}</div>\n      </>\n    )\n  }\n}\n\nexport default connect(\n  'Home',\n  mapProps\n)(TSCounter)\n```\n</p>\n</details>\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<\n  CounterState,\n  CounterActionParams\n> = {\n  actions: {\n    increment: (params) => {\n      return (state) => {\n        state.count += params\n      }\n    }\n  },\n  state: { count: 0 }\n}\n\n// v4.1.x\nconst Counter = createStore(() => {\n  const [state, setState] = useModel({ count: 0 })\n  const actions = {\n    increment: (params) => {\n      setState((state) => {\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 = () => {\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<State, ActionParams> = {\n  state: {\n    counter: 1\n    enable: false\n  },\n  actions: {\n    add: (payload) => state => {\n      state.counter += payload\n    },\n    switch: () => state => {\n      state.enable = !state.enable\n    }\n  }\n}\n```\n\n```ts\nconst Component = () => {\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 => 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 }) => ['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) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: Error) => 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) => {\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      <div className=\"App\">\n        <Counter />\n        <Counter />\n        <Counter />\n      </div>\n    )\n  }\n}\n```\n\n<details>\n<summary>Counter model</summary>\n<p>\n\n```ts\ninterface State {\n  count: number\n}\n\ninterface ActionParams {\n  increment: number\n}\n\nconst model: ModelType<State, ActionParams> = {\n  state: {\n    count: 0\n  },\n  actions: {\n    increment: payload => {\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 => (state.count += payload)\n      return state => {\n        state.count += payload\n      }\n    }\n  }\n}\n\n```\n\n</p>\n</details>\n\n<details>\n<summary>Counter.tsx</summary>\n<p>\n\n```tsx\n\nconst Counter = () => {\n  const [{ useStore }] = useState(() => Model(model))\n  const [state, actions] = useStore()\n  return (\n    <div>\n      <div>{state.count}</div>\n      <button onClick={() => actions.increment(3)}>Increment</button>\n    </div>\n  )\n}\n\nexport default Counter\n```\n\n</p>\n</details>\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<ExpensiveState, ExpensiveActionParams> = {\n  state: {\n    moduleList: []\n  },\n  actions: {\n    setPreFreezedDataset: () => {\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 => {\n    // Not allowed\n    // return state => (state.count += payload)\n    return state => {\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) => {\n  await timeout(1000, {})\n  context.next(restMiddlewares)\n}\n\nconst nextCounterModel: ModelType<CounterState, NextCounterActionParams> = {\n  actions: {\n    add: num => {\n      return state => {\n        state.count += num\n      }\n    },\n    increment: async (num, { actions }) => {\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","readmeFilename":"README.md","gitHead":"e7126efec5cf8fc858ecc868d30dede4a231bff0","_id":"react-model@4.3.0-alpha.3","_nodeVersion":"16.19.0","_npmVersion":"8.19.3","dist":{"integrity":"sha512-7zkPXsbmXzk9ppTaYGO2IxEBZjuPwJQA93VJoRr8hWEp5S4ydK+RvFyIos8oACUjlcvw8TSl0BV8tTdBe23s4Q==","shasum":"1c77c192c73fa1ea7e710a868788524465f3a5cc","tarball":"https://registry.npmjs.org/react-model/-/react-model-4.3.0-alpha.3.tgz","fileCount":22,"unpackedSize":114916,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCICq8ZFAyPxARQK8upYGzvdCf0/JjnlbLGNEMdrA4VOFJAiA5nh8hFCEPnNlHzrwJgObqEhqKhMCk1767k2j8VOkYmw=="}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJkHVX2ACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmqfVA/+Mi5LuCC1FJB9yUIh4WkTBqtL3QxGANxfQnCUl0V+sr207QJ4\r\nlX1oSLJpZpOD4FqbVMV6IkRGYCGiXT5tXyM+ijt/WF6uwOjkRSdklO1FPmi7\r\nz+qiDzolG8oai/5q5k8pLb+9BC2Ri9DHlrPU4fMwcwnpjYYNm9n1avzMeOBa\r\npVRecBeI/gLwyqLasAy7umDy02dR135oRaFNoKAIzdKspX6nGnbht1A9/mR+\r\nwXUKsX0y045j7RlnKrm4wWWhxDRtQKtdlwrrcT29cMEJwUBFjJQ5rziZGrVZ\r\nyvM6XdfRZqXiqs17/ZupqJVl6z14Cmcl+MFyRldGv+nMmGQrCtYWeiG2O4/r\r\nBi/sX8JQrkE7/gKFL5zx0Ve2VB9bjhQ0RgST3kGdRdt36IWCWLQ2ER2koHo+\r\nkNA/tqUN0Ziip/mbBoZ2ZEyuIT1AMRvxx55xRbMhwLEM7K9y1KVSTgNl1eAf\r\nFrJDvSrntVpabWWSL/B/64AugC+rf4vPnmHaUzOkFaa+eXQ6acnEMglVgErE\r\nW3Ha3s5zX6AvtCrLnVIhhRykI9rtnrevPCAGAx+ZqoJ3Gr9ZftOpBb7Wy9m9\r\nTFBUIgAdHe+a1g4cA7x8YANqG2GOg0jyg6DF6CIm22JS4fs6pDCa4vL/oMpb\r\nVbcrabQdsqmKPB0yx1rDizD2yNw8HfJPyRo=\r\n=5NOs\r\n-----END PGP SIGNATURE-----\r\n"},"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"maintainers":[{"name":"leecade","email":"leecade@163.com"},{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_4.3.0-alpha.3_1679644150137_0.25352092591445285"},"_hasShrinkwrap":false},"4.3.0":{"name":"react-model","version":"4.3.0","description":"The State management library for React","main":"./dist/react-model.js","module":"./dist/react-model.esm.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build:prod":"microbundle --define process.env.NODE_ENV=production --sourcemap false --jsx React.createElement --output dist --tsconfig ./tsconfig.json","build:dev":"microbundle --define process.env.NODE_ENV=development --sourcemap true --jsx React.createElement --output dist --tsconfig ./tsconfig.json","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":">=8.0.1"},"peerDependencies":{"react":">=16.3.0","react-dom":">=16.3.0","typescript":">=3.9.0"},"devDependencies":{"@commitlint/cli":"^16.3.0","@commitlint/config-conventional":"^16.2.4","@testing-library/react":"^10.0.1","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/faker":"^5.5.3","@types/jest":"^25.1.0","@types/node":"^14.0.0","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","faker":"^5.5.3","husky":"^4.0.2","jest":"^24.1.0","microbundle":"^0.12.3","prettier":"^2.0.0","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^10.0.1","remark-lint":"^9.1.1","remark-preset-lint-recommended":"^6.1.2","ts-jest":"^26.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"resolutions":{"minimist":">=1.2.6"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"browserslist":["edge 17","firefox 70","chrome 48","safari 12.1","android 4.0","samsung 9.2"],"gitHead":"e7126efec5cf8fc858ecc868d30dede4a231bff0","_id":"react-model@4.3.0","_nodeVersion":"16.20.0","_npmVersion":"8.19.4","dist":{"integrity":"sha512-xu6KY8kRHURhXM9lVtZ1uD0rkZdlSGMAHXwW21yvIcH+gwobCaXgRvJYRk556y2q2S4UgOW+n3eXVkx7nAeRig==","shasum":"ce6405a7d18efa6f84b1a2ec8ee847e613cd3e71","tarball":"https://registry.npmjs.org/react-model/-/react-model-4.3.0.tgz","fileCount":22,"unpackedSize":114715,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIE3Xh7IIADUyc8UnWXW0rB1vncn4eWKKoDmYo+dDtR7EAiEAoDNwfiaM76aX0ErISK8dc9BBcTPt66UhklGdNFxI720="}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJkRQNQACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmrruQ//V/KklUHyfpZzRN6Myip0YCMNu+hagU/iY3j1CdYuZqVsVzlV\r\nNp+ln2mJXsF6uYLqHYxt4YHoHsIWhKDP8+CPz0v8yNx2Ov0YmAH4rsNeFXej\r\n9dRUXU2C7eKWZb/xx4CpJ3ab8TfH91w+pXVRwVYlREk9mkAHJFBuPUMVYOl4\r\nFTGVMCFboLK+WR/TR/aERJmP8y2wAo4iIh25OffnVUojvaK6NQ3KvgcljKoY\r\nb3iIi7i7+ZcFQljSb6Et+D7yG5YJaXopWgEBJA7FHEFbTbRsEYPp++1TARP1\r\nJ1WTmTZaIFFCFoX2dV/HZ0NSVbGibYRtsZiYu09pm+492I6Og5a1J+S8mI/b\r\nlc+FZBswnGeT1JVOotNyqTV8mmCHNZEsMXPmnPSxpFtqySY4F/8PCbqt9nE2\r\no0UYW9hmTEAuJu09W/nVP/NK2deAIxhLxHY1bngZ8PloD1VUQ+GAm3QZGU3E\r\nwUVMKJwVPhVrjyaKqBFHs0Ghz89gP6khBSLT4EK5wGJ1YMelN3Tjxy5HEOAh\r\nwy938fCw5kQCxkb3nGx1jwZKaKqJgvpzSE+GNufanAce0iH46vspvAMjrEPN\r\nNgVlbB9k++LvpSidQ33/mUMSZsj+wn8QHU0EYPufYRZRDZHn26TIE+bzVu6t\r\nRGmC9ZP6tENtto/utq3Q5OvrX41lphBPSC8=\r\n=B7Vd\r\n-----END PGP SIGNATURE-----\r\n"},"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"maintainers":[{"name":"leecade","email":"leecade@163.com"},{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_4.3.0_1682244432439_0.20219666945226522"},"_hasShrinkwrap":false},"4.3.1":{"name":"react-model","version":"4.3.1","description":"The State management library for React","main":"./dist/react-model.js","module":"./dist/react-model.esm.js","umd:main":"./dist/react-model.umd.js","types":"./src/index","scripts":{"build:prod":"microbundle --define process.env.NODE_ENV=production --sourcemap false --jsx React.createElement --output dist --tsconfig ./tsconfig.json","build:dev":"microbundle --define process.env.NODE_ENV=development --sourcemap true --jsx React.createElement --output dist --tsconfig ./tsconfig.json","commit":"git-cz","lint-ts":"tslint -c tslint.json 'src/**/*.ts'","lint-md":"remark .","test":"jest --silent","test:coverage":"jest --collect-coverage --silent"},"keywords":["react","model","state-management","react-hooks"],"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"license":"MIT","dependencies":{"immer":">=8.0.1 <10.0.0"},"peerDependencies":{"react":">=16.3.0","react-dom":">=16.3.0","typescript":">=3.9.0"},"devDependencies":{"@commitlint/cli":"^16.3.0","@commitlint/config-conventional":"^16.2.4","@testing-library/react":"^10.0.1","@testing-library/react-hooks":"^2.0.1","@types/babel__core":"^7.1.1","@types/babel__template":"^7.0.2","@types/faker":"^5.5.3","@types/jest":"^25.1.0","@types/node":"^14.0.0","@types/react":"^16.9.1","@types/react-dom":"^16.8.0","commitizen":"^4.0.0","cz-conventional-changelog":"^3.0.0","faker":"^5.5.3","husky":"^4.0.2","jest":"^24.1.0","microbundle":"^0.12.3","prettier":"^2.0.0","react":"^16.8.4","react-dom":"^16.8.4","react-test-renderer":"^16.8.6","remark-cli":"^10.0.1","remark-lint":"^9.1.1","remark-preset-lint-recommended":"^6.1.2","ts-jest":"^26.0.0","tslint":"^5.14.0","typescript":"^3.4.5"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}},"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"resolutions":{"minimist":">=1.2.6"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"homepage":"https://github.com/byte-fe/react-model#readme","config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"browserslist":["edge 17","firefox 70","chrome 48","safari 12.1","android 4.0","samsung 9.2"],"gitHead":"e9dfb94cc71d23919ab3152e4738efc68414fc98","_id":"react-model@4.3.1","_nodeVersion":"16.20.0","_npmVersion":"8.19.4","dist":{"integrity":"sha512-C19K5EUTTETki5IVqppOD8u+xkJvwL6w/RK8SHLZ6cvU1YSjvscKiUGfYsQHxouk6OhdoSQYHkDbxxLanPs72A==","shasum":"e40b9f87aec648c893c797405867aff722ccfca8","tarball":"https://registry.npmjs.org/react-model/-/react-model-4.3.1.tgz","fileCount":22,"unpackedSize":114916,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIHa4kLYcVfPnZjZI1vHcoOJdhHk4rfDehAY3JB1Abus/AiB8sJ7QvvTJV3VOch6GELzNxJT0L8+dKLNT7kYNLK7lPA=="}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJkRQPwACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmql6Q//cX9owwVWc2OHQsoUeFwa8joHv4pDJx3j10q9TlVbaQjPq33y\r\n3C7+o9/o/JvTZZYX5ccw/oJKSK2zbSdDv7n8GSk62Sv65Zk3MWmNFpwdlsAx\r\n4XDK9YZtCzXA8ISu5b8fbBm47l4HhQTGFmw20d1ZgODuIevXwucCyo90ORIP\r\np8L11lwZPvvT8hhyJXQ+QdB0oH+X8JBcAJYAyQpiH1Kx5aEL9okoFtebHWhY\r\nUCZ4lKjt03qPP6crc0xNhA8IrXwB8jDM0311NYgKT1KLD6howcEy1go9AcID\r\nb0aLaenLkG8TRuRAj02pmIAKtoTFWOkapQSdNZ/qFaSNeD+ocoAs19ZTZYyE\r\nLSuX+AaM0Gu5HOhMuxIqVhe9MPvNeqQTnts10xc9OeS3o91Zf+oWq3MJHJr5\r\nehNmZXVI48wr63xHfXST/2gCXiel8Mqv4k0WFkSanQP8F+Yb2fpTPE5rgUYm\r\nEskMRavDIES0d5ZIjURmmrHYwh2Os91We6zMQNg3wKzfmUxA2YLVD/0GvF8a\r\ntMmntaImNLhfbb41W8Ks8G8rhAknxNHq1iCeRX1hKk8OJhVjomSnQO442yxY\r\n2KzJvBfU0BjclHDbmjfo48nfthmyekJ8nLN6GQE2dEvehZhm+307nqd8ZXC2\r\nMMshlHby3QR8vsKxmsGN1o65z1Mgpbd4GRs=\r\n=lj05\r\n-----END PGP SIGNATURE-----\r\n"},"_npmUser":{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"},"directories":{},"maintainers":[{"name":"leecade","email":"leecade@163.com"},{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/react-model_4.3.1_1682244592264_0.6757419480477551"},"_hasShrinkwrap":false}},"readme":"# react-model &middot; ![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 = () => {\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 = () => {\n  return <TodoList />\n}\n\nconst TodoList = () => {\n  const { items, setItems } = useStore()\n  return <div>\n    <Addon handler={setItems} />\n    {state.items.map((item, index) => (<Todo key={index} item={item} />))}\n  </div>\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(() => {\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 = () => {\n    setLocalCount(localCount + 1)\n  }\n  const inc = () => {\n    setCount(c => 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 = () => {\n  const { count, localCount, inc, incLocal } = useStore()\n  return <div>\n    <span>count: { count }</span>\n    <span>localCount: { localCount }</span>\n    <button onClick={inc}>inc</button>\n    <button onClick={incLocal}>incLocal</button>\n  </div>\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (payload, { state }) => {\n      return {\n        counter: state.counter + (payload || 1)\n      }\n    },\n    openLight: async (_, { state, actions }) => {\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 () => {\n      await new Promise((resolve, reject) =>\n        setTimeout(() => {\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<typeof Model.actions>\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 () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button onClick={e => actions.increment(33)}>home increment</button>\n      <button onClick={e => sharedActions.increment(20)}>\n        shared increment\n      </button>\n      <button onClick={e => actions.get()}>fake request</button>\n      <button onClick={e => actions.openLight()}>fake nested call</button>\n    </div>\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 >= 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> Hint: The state returned should only be used as readonly\n\n```jsx\nimport { useStore, getState } from '../model/index'\n\nconst BasicHook = () => {\n  const [state, actions] = useStore('Counter')\n  useEffect(() => {\n    console.log('some mounted actions from BasicHooks')\n    return () =>\n      console.log(\n        `Basic Hooks unmounted, current Counter state: ${JSON.stringify(\n          getState('Counter')\n        )}`\n      )\n  }, [])\n  return (\n    <>\n      <div>state: {JSON.stringify(state)}</div>\n    </>\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: () => {\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 = () => {\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<StateType, ActionsParamType> = {\n  actions: {\n    increment: async (params, { state: s }) => {\n      // return (state: typeof s) => { // TypeScript < 3.9\n      return state => {\n        state.counter += params || 1\n      }\n    },\n    decrease: params => s => {\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) => {\n      return state => {\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<details>\n<summary>Store: shared.ts</summary>\n<p>\n\n```ts\nconst initialState = {\n  counter: 0\n}\n\nconst model: ModelType<StateType, ActionsParamType> = {\n  actions: {\n    increment: (params, { state }) => {\n      return {\n        counter: state.counter + (params || 1)\n      }\n    }\n  },\n  // Provide for SSR\n  asyncState: async context => {\n    await waitFor(4000)\n    return { counter: 500 }\n  },\n  state: initialState\n}\n\nexport default model\n```\n\n</p>\n</details>\n\n<details>\n<summary>Global Config: _app.tsx</summary>\n<p>\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) => {\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    <Container>\n      <Component {...pageProps} />\n    </Container>\n  )\n}\n\nMyApp.getInitialProps = async (context: NextAppContext) => {\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</p>\n</details>\n\n<details>\n<summary>Page: hooks/index.tsx</summary>\n<p>\n\n```tsx\nimport { useStore, getState } from '../index'\nexport default () => {\n  const [state, actions] = useStore('Home')\n  const [sharedState, sharedActions] = useStore('Shared')\n\n  return (\n    <div>\n      Home model value: {JSON.stringify(state)}\n      Shared model value: {JSON.stringify(sharedState)}\n      <button\n        onClick={e => {\n          actions.increment(33)\n        }}\n      >\n    </div>\n  )\n}\n```\n</p>\n</details>\n\n<details>\n<summary>Single Page Config: benchmark.tsx</summary>\n<p>\n\n```tsx\n// ...\nBenchmark.getInitialProps = async () => {\n  return await getInitialState({ modelName: 'Todo' }, { isServer: true })\n}\n```\n</p>\n</details>\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<{}> = async (context, restMiddlewares) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: any) => 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) => async (params: any) => {\n  const context: Context = {\n    modelName,\n    setState,\n    actionName: action.name,\n    next: () => {},\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<\n  { name: string }, // State Type\n  { ext: undefined }, // ActionParamsType\n  { name: string } // ExtContextType\n> = {\n  actions: {\n    // { state, action } => { state, action, [name] }\n    ext: (_, { name }) => {\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 => '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      <Provider>\n        <Counter />\n      </Provider>\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<details>\n<summary>Javascript decorator version</summary>\n<p>\n\n```jsx\nimport React, { PureComponent } from 'react'\nimport { Provider, connect } from 'react-model'\n\nconst mapProps = ({ light, counter }) => ({\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      <>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(5)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n      </>\n    )\n  }\n}\n```\n\n</p>\n</details>\n\n<details>\n<summary>TypeScript Version</summary>\n<p>\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) => ({\n  lightStatus: light ? 'open' : 'close',\n  counter,\n  response\n})\n\ntype RType = ReturnType<typeof mapProps>\n\nclass TSCounter extends PureComponent<\n  { state: RType } & { actions: ActionType }\n> {\n  render() {\n    const { state, actions } = this.props\n    return (\n      <>\n        <div>TS Counter</div>\n        <div>states - {JSON.stringify(state)}</div>\n        <button onClick={e => actions.increment(3)}>increment</button>\n        <button onClick={e => actions.openLight()}>Light Switch</button>\n        <button onClick={e => actions.get()}>Get Response</button>\n        <div>message: {JSON.stringify(state.response)}</div>\n      </>\n    )\n  }\n}\n\nexport default connect(\n  'Home',\n  mapProps\n)(TSCounter)\n```\n</p>\n</details>\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<\n  CounterState,\n  CounterActionParams\n> = {\n  actions: {\n    increment: (params) => {\n      return (state) => {\n        state.count += params\n      }\n    }\n  },\n  state: { count: 0 }\n}\n\n// v4.1.x\nconst Counter = createStore(() => {\n  const [state, setState] = useModel({ count: 0 })\n  const actions = {\n    increment: (params) => {\n      setState((state) => {\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 = () => {\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<State, ActionParams> = {\n  state: {\n    counter: 1\n    enable: false\n  },\n  actions: {\n    add: (payload) => state => {\n      state.counter += payload\n    },\n    switch: () => state => {\n      state.enable = !state.enable\n    }\n  }\n}\n```\n\n```ts\nconst Component = () => {\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 => 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 }) => ['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) => {\n  const { next } = context\n  await next(restMiddlewares).catch((e: Error) => 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) => {\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      <div className=\"App\">\n        <Counter />\n        <Counter />\n        <Counter />\n      </div>\n    )\n  }\n}\n```\n\n<details>\n<summary>Counter model</summary>\n<p>\n\n```ts\ninterface State {\n  count: number\n}\n\ninterface ActionParams {\n  increment: number\n}\n\nconst model: ModelType<State, ActionParams> = {\n  state: {\n    count: 0\n  },\n  actions: {\n    increment: payload => {\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 => (state.count += payload)\n      return state => {\n        state.count += payload\n      }\n    }\n  }\n}\n\n```\n\n</p>\n</details>\n\n<details>\n<summary>Counter.tsx</summary>\n<p>\n\n```tsx\n\nconst Counter = () => {\n  const [{ useStore }] = useState(() => Model(model))\n  const [state, actions] = useStore()\n  return (\n    <div>\n      <div>{state.count}</div>\n      <button onClick={() => actions.increment(3)}>Increment</button>\n    </div>\n  )\n}\n\nexport default Counter\n```\n\n</p>\n</details>\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<ExpensiveState, ExpensiveActionParams> = {\n  state: {\n    moduleList: []\n  },\n  actions: {\n    setPreFreezedDataset: () => {\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 => {\n    // Not allowed\n    // return state => (state.count += payload)\n    return state => {\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) => {\n  await timeout(1000, {})\n  context.next(restMiddlewares)\n}\n\nconst nextCounterModel: ModelType<CounterState, NextCounterActionParams> = {\n  actions: {\n    add: num => {\n      return state => {\n        state.count += num\n      }\n    },\n    increment: async (num, { actions }) => {\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","maintainers":[{"name":"leecade","email":"leecade@163.com"},{"name":"arrayzoneyour","email":"hustliyidong@gmail.com"}],"time":{"modified":"2023-04-23T10:09:52.571Z","created":"2014-04-30T21:13:12.551Z","0.0.0":"2014-04-30T21:13:14.761Z","2.0.2":"2019-01-15T08:18:54.294Z","2.0.3":"2019-01-15T08:26:43.618Z","2.0.4":"2019-01-15T08:40:17.467Z","2.0.5":"2019-01-16T08:19:41.105Z","2.0.6-unstable.1":"2019-01-17T15:45:58.714Z","2.0.6":"2019-01-17T17:17:17.831Z","2.0.7":"2019-01-18T03:41:55.726Z","2.0.8":"2019-01-18T03:54:32.690Z","2.0.9":"2019-01-20T13:33:38.949Z","2.0.10":"2019-01-20T14:09:57.330Z","2.0.11":"2019-01-20T16:13:23.191Z","2.0.12":"2019-01-21T06:17:03.118Z","2.0.13":"2019-01-22T03:38:41.234Z","2.1.0":"2019-02-06T14:49:53.726Z","2.2.0":"2019-02-17T07:17:00.038Z","2.2.1":"2019-02-19T05:45:38.358Z","2.2.2":"2019-02-22T11:03:59.025Z","2.3.0":"2019-02-25T09:19:49.923Z","2.3.1":"2019-02-26T05:38:17.218Z","2.3.2":"2019-03-01T06:50:24.643Z","3.0.0-alpha.0":"2019-03-04T10:53:46.237Z","2.4.0":"2019-03-07T11:22:20.871Z","2.4.1":"2019-03-12T06:45:00.893Z","2.4.2":"2019-03-12T08:20:17.112Z","2.5.0":"2019-03-12T13:33:50.133Z","2.5.1":"2019-03-15T10:44:39.948Z","2.5.2":"2019-03-15T10:58:45.339Z","2.5.3":"2019-03-15T11:00:15.608Z","2.6.0-alpha.0":"2019-03-22T14:12:57.117Z","2.6.0":"2019-03-27T07:23:19.180Z","3.0.0-alpha.1":"2019-04-03T10:11:51.789Z","2.7.0-alpha.0":"2019-04-03T13:38:36.523Z","2.7.0-alpha.1":"2019-04-08T02:49:56.542Z","2.7.0":"2019-04-12T08:27:04.393Z","2.7.1-alpha.0":"2019-04-22T11:28:20.834Z","2.7.1-alpha.1":"2019-04-22T11:59:37.745Z","2.7.1-alpha.2":"2019-04-24T12:42:59.413Z","2.7.1":"2019-04-24T14:17:23.064Z","2.7.2-alpha.0":"2019-05-15T06:25:01.397Z","2.7.2":"2019-05-15T06:51:28.121Z","3.0.0-alpha.2":"2019-05-27T08:44:08.498Z","3.0.0-alpha.3":"2019-05-28T10:44:54.796Z","3.0.0-alpha.4":"2019-06-25T07:02:03.555Z","3.0.0-rc":"2019-09-05T07:44:03.399Z","3.0.0-rc.0":"2019-09-05T07:49:47.214Z","3.0.0":"2019-09-05T09:31:04.983Z","3.0.1":"2019-09-23T11:43:15.202Z","3.0.2":"2019-10-08T07:02:26.081Z","3.0.3":"2019-10-23T09:56:54.043Z","3.0.4-rc.0":"2020-03-10T07:46:25.072Z","3.0.4":"2020-03-10T10:06:42.179Z","3.1.0-dev.20200313.0":"2020-03-13T13:41:42.547Z","3.1.0-dev.20200313.1":"2020-03-13T15:22:00.829Z","3.1.0":"2020-03-23T02:52:39.485Z","3.1.1-rc.0":"2020-04-01T08:23:54.695Z","3.1.1":"2020-05-18T13:21:55.917Z","3.1.2":"2020-05-19T12:22:08.812Z","4.0.0-rc.0":"2020-05-19T12:31:29.516Z","4.0.0-dev.20200615.0":"2020-06-15T08:17:09.948Z","4.0.0-dev.20200615.1":"2020-06-15T08:37:45.720Z","3.1.3":"2020-07-08T07:05:45.220Z","3.1.4":"2020-07-08T10:19:51.664Z","4.0.0-rc.1":"2020-07-29T04:11:02.501Z","4.0.0-rc.2":"2020-09-24T06:39:38.862Z","4.0.0-rc.3":"2020-09-24T09:17:07.695Z","4.0.0-rc.4":"2020-09-29T10:34:26.238Z","4.0.0-rc.5":"2020-09-29T11:07:26.106Z","4.0.0-rc.6":"2020-09-29T13:09:59.651Z","4.0.0":"2021-03-05T05:09:09.027Z","4.0.1-rc.0":"2021-04-15T07:30:38.048Z","4.0.1-rc.1":"2021-04-22T10:12:30.975Z","4.0.1":"2021-06-17T07:58:31.268Z","4.1.0-alpha.0":"2021-06-18T04:03:17.494Z","4.1.0-alpha.1":"2021-06-18T08:06:53.115Z","4.1.0-alpha.2":"2021-06-21T11:51:07.902Z","4.0.2":"2021-06-25T03:07:12.360Z","4.1.0-alpha.3":"2021-06-28T13:27:19.451Z","4.1.0-rc.0":"2021-07-05T13:55:52.516Z","4.1.0-rc.1":"2021-07-28T09:43:21.471Z","4.1.0":"2021-08-03T12:09:46.318Z","4.1.1-alpha.0":"2021-08-04T12:20:43.754Z","4.1.1":"2021-08-09T07:51:41.749Z","4.1.2-rc.0":"2021-09-06T08:22:08.339Z","4.1.2":"2021-09-06T08:56:42.476Z","4.1.3":"2021-09-09T13:47:28.839Z","4.1.4":"2021-09-10T08:08:34.328Z","4.2.0-rc.0":"2021-09-28T06:20:36.728Z","4.2.0-rc.1":"2021-09-30T04:22:28.186Z","4.2.0-rc.2":"2021-11-16T13:10:00.912Z","4.2.0":"2021-11-16T13:31:02.683Z","4.3.0-alpha.0":"2021-12-29T03:59:09.371Z","5.0.0-alpha.0":"2021-12-29T06:05:50.582Z","4.2.1-rc.0":"2022-01-11T07:46:43.750Z","4.2.1":"2022-01-13T06:13:25.154Z","4.3.0-alpha.1":"2022-02-20T15:07:36.791Z","4.3.0-rc.0":"2022-03-01T07:59:02.542Z","4.3.0-alpha.2":"2022-09-14T14:34:33.686Z","4.3.0-alpha.3":"2023-03-24T07:49:10.316Z","4.3.0":"2023-04-23T10:07:12.672Z","4.3.1":"2023-04-23T10:09:52.460Z"},"readmeFilename":"README.md","keywords":["react","model","state-management","react-hooks"],"repository":{"type":"git","url":"git+https://github.com/byte-fe/react-model.git"},"author":{"name":"ArrayZoneYour","email":"hustliyidong@gmail.com"},"bugs":{"url":"https://github.com/byte-fe/react-model/issues"},"license":"MIT","homepage":"https://github.com/byte-fe/react-model#readme"}