{"id":14987512,"url":"https://github.com/diablow/zustand-store-addons","last_synced_at":"2025-05-05T04:04:06.003Z","repository":{"id":43260534,"uuid":"292181909","full_name":"Diablow/zustand-store-addons","owner":"Diablow","description":"React state management addons for zustand.","archived":false,"fork":false,"pushed_at":"2023-04-07T20:35:32.000Z","size":153,"stargazers_count":96,"open_issues_count":4,"forks_count":5,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-05-05T04:03:59.382Z","etag":null,"topics":["hooks","javascript","react","state-management","typescript","zustand"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Diablow.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-09-02T04:50:25.000Z","updated_at":"2025-02-24T03:26:05.000Z","dependencies_parsed_at":"2024-06-20T19:08:12.627Z","dependency_job_id":"daa01c7a-6e63-49c0-b31f-2bf7eaa56b7a","html_url":"https://github.com/Diablow/zustand-store-addons","commit_stats":{"total_commits":18,"total_committers":3,"mean_commits":6.0,"dds":"0.16666666666666663","last_synced_commit":"cf1ca88a33a09c4cc91520276ef6ed7ea3e91a59"},"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Diablow%2Fzustand-store-addons","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Diablow%2Fzustand-store-addons/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Diablow%2Fzustand-store-addons/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Diablow%2Fzustand-store-addons/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Diablow","download_url":"https://codeload.github.com/Diablow/zustand-store-addons/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252436291,"owners_count":21747470,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["hooks","javascript","react","state-management","typescript","zustand"],"created_at":"2024-09-24T14:14:51.542Z","updated_at":"2025-05-05T04:04:05.983Z","avatar_url":"https://github.com/Diablow.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"## Zustand Store Addons for React\n\nCreate zustand stores with the leverage of powerful features inspired by Vue.js component's state management.\n\nIf you're new to zustand you can read the docs [here](https://github.com/react-spring/zustand)\n\n[Live Demo](https://codesandbox.io/s/zustand-store-addons-demo-dts8y?file=/src/App.js)\n\n## Included Features\n\n* **Computed properties**.\n\n* **Watchers**.\n\n* **Simplified fetch syntax**.\n\n* **Middleware chaining**.\n\n* **Automatic logs** for operations.\n\n## Installation\n\n```bash\nnpm install zustand zustand-store-addons\n```\n\nor\n\n```bash\nyarn add zustand zustand-store-addons\n```\n\n**Note: *Requires zustand version \u003e= 3.0.0*\n\n## But... Why not a middleware?\n\nAlthough middleware can help you add extra functionality *it scope is limited to what is being passed to the create function and attached once the initial state setup has completed*. Some of the included features can't be possible because of this.\n\n---\n\n# Addons Object\n\nWhen we setup a store using this package we can pass an object as a second parameter to the create function with the following properties: [computed](#computed-properties-addonscomputed), [watchers](#watchers-addonswatchers), [middleware](#middleware-chaining-addonsmiddleware) and [settings](#log-settings-addonssettings).\n\n```jsx\nconst useStore = create((set, get) =\u003e ({\n    welcomeMessage: 'Hello there!'\n  }),\n  // Addons object\n  {\n    computed: {},\n    watchers: {},\n    middleware: [],\n    settings: {}\n  }\n)\n```\n\nIf the addons object is not provided the only feature we can still use would be the [simplified fetch syntax](#simplified-fetch-syntax).\n\n# How to use it, and why use it\n\nWe're going to start with a conventional zustand store\n\n```jsx\nimport create from 'zustand-store-addons';\n\nconst useStore = create((set, get) =\u003e ({\n  count: 0,\n  increment: () =\u003e set(state =\u003e ({ count: state.count + 1 })),\n});\n\nexport default AnotherCounterComponent() {\n  const count = useStore(state =\u003e state.count);\n  const increment = useStore(state =\u003e state.increment);\n\n  return (\n    \u003cdiv\u003e\n      \u003cp\u003eCount: {count}\u003c/p\u003e\n      \u003cbutton type=\"button\" onClick={increment}\u003e\n        Increment\n      \u003c/button\u003e\n    \u003c/div\u003e\n  )\n}\n```\n\nOk, at this point we feel the need to display **count multiplied by 2** and a **total** representing the sum of both values, so we do the following:\n\n```jsx\nexport default AnotherCounterComponent() {\n  const count = useStore(state =\u003e state.count);\n  const increment = useStore(state =\u003e state.increment);\n  const doubleCount = count * 2; // \u003c--\n  const total = count + doubleCount; // \u003c--\n\n  return (\n    \u003cdiv\u003e\n      \u003cp\u003eCount: {count}\u003c/p\u003e\n      \u003cp\u003eCount*2: {doubleCount}\u003c/p\u003e\n      \u003chr /\u003e\n      \u003cp\u003eTotal: {total}\u003c/p\u003e\n      \u003cbutton type=\"button\" onClick={increment}\u003e\n        Increment\n      \u003c/button\u003e\n    \u003c/div\u003e\n  )\n}\n```\n\nWe are now calculating the `doubleCount` and `total` values **inside the component**.\nEverything looks good until we realize that we need to **have access to these values from other components too** –that's the whole idea of using a \"global/context\" state management– and they are not descendants of this component (*prop drilling* is not a practical solution).\n\nWouldn't be great if we could calculate `doubleCount` and `total` in the store? Now we can!\n\nLet's pass an object ([addons object](#addons-object)) as a second argument to the create store function with a `computed` key in order to list our **computed properties**\n\n## Computed properties (addons.computed)\n\n```jsx\nimport create from 'zustand-store-addons';\n\nconst useStore = create((set, get) =\u003e ({\n  count: 0,\n  increment: () =\u003e set(state =\u003e ({ count: state.count + 1 })),\n}), {\n  computed: {\n    doubleCount: function() {\n      // `this` points to the state object\n      return this.count * 2\n    },\n    // Shorthand method definition\n    total() {\n      return this.count + this.doubleCount;\n    }\n  }\n};\n```\n\nThe above will result in the following state:\n\n```jsx\n{\n  count: 0,\n  increment: function () { /* Increment fn logic */ },\n  doubleCount: 0,\n  total: 0,\n}\n```\n\nFor each key contained in the computed object, a property –named after the key– will be added to the state, and the provided function will be used as the getter function.\n\n***Inside the getter functions we use the `this` keyword which points to the state, for this reason we should not use arrow functions to define them***.\n\nNow we need to update our component\n\n```jsx\nexport default AnotherCounterComponent() {\n  // This is getting crowded... Is this the best way?\n  const count = useStore(state =\u003e state.count);\n  const increment = useStore(state =\u003e state.increment);\n  const doubleCount = useStore(state =\u003e state.doubleCount);\n  const total = useStore(state =\u003e state.total);\n\n  return (\n    \u003cdiv\u003e\n      \u003cp\u003eCount: {count}\u003c/p\u003e\n      \u003cp\u003eCount*2: {doubleCount}\u003c/p\u003e\n      \u003chr /\u003e\n      \u003cp\u003eTotal: {total}\u003c/p\u003e\n      \u003cbutton type=\"button\" onClick={increment}\u003e\n        Increment\n      \u003c/button\u003e\n    \u003c/div\u003e\n  )\n}\n```\n\nIn the code above we are *selecting* properties from the store individually, what are our options to save space or typing fatigue 😆?\n\nPerhaps use an array:\n\n```jsx\nconst [count, increment, doubleCount, total] = useStore(\n  state =\u003e [state.count, state.increment, state.doubleCount, state.total]\n)\n```\n\nIf we leave the code above as it is right now with any change in the store –even not selected properties– our component will re-render in order to keep the pace. We don't want that behavior, let's add zustand's **shallow** function to prevent it:\n\n```jsx\nconst [count, increment, doubleCount, total] = useStore(\n  state =\u003e [state.count, state.increment, state.doubleCount, state.total]\n, shallow)\n```\n\nIs this better? It seems repetitive. Let's take a look at a different approach **simplified fetch syntax**.\n\n## Simplified fetch syntax\n\nWe can list our selection using a string separating the properties with a comma between them. It is case-sensitive, white space is ignored and uses the **shallow** function internally. This works for a single or multiple properties.\n\n```jsx\n// Single property\nconst increment = useStore('increment');\n\n// Returns an array when selecting multiple properties\nconst [count, increment, doubleCount, total] = useStore(\n  'count, increment, doubleCount, total'\n)\n\n// Or use template literals/strings if you need\nconst times = 'double';\nconst [count, increment, doubleCount, total] = useStore(\n  `count, increment, ${times}Count, total`\n)\n```\n\nSo, let's go back to our example and apply this to clean our component's code a little bit.\n\n```jsx\nexport default AnotherCounterComponent() {\n  const [count, increment, doubleCount, total] = useStore(\n    'count, increment, doubleCount, total'\n  )\n\n  return (\n    \u003cdiv\u003e\n      \u003cp\u003eCount: {count}\u003c/p\u003e\n      \u003cp\u003eCount x 2: {doubleCount}\u003c/p\u003e\n      \u003chr /\u003e\n      \u003cp\u003eTotal: {total}\u003c/p\u003e\n      \u003cbutton type=\"button\" onClick={increment}\u003e\n        Increment\n      \u003c/button\u003e\n    \u003c/div\u003e\n  )\n}\n```\n\nThis is looking good! It's time to add logs to our store in order to see how the state is being *mutated*. We're going to use a middleware function.\n\nIf we were implementing a middleware function with a standard zustand store we would need to wrap the *create* function parameters with it. If we wanted to use another one we would wrap the previous one and so on e.g., `useStore(mw2(mw1((set, get) =\u003e ({...}))))` but this is not a standard store, so we can use **middleware chaining**.\n\n## Middleware chaining (addons.middleware)\n\nEasy way to add middleware to our stores using an array. This will apply the functions using the element's order so you don't need to worry about the wrapping.\n\n```jsx\nimport create from 'zustand-store-addons';\n\nconst log = config =\u003e (set, get, api) =\u003e config(args =\u003e {\n  console.log(\"  applying\", args)\n  set(args)\n  console.log(\"  new state\", get())\n}, get, api)\n\nconst useStore = create((set, get) =\u003e ({\n  count: 0,\n  increment: () =\u003e set(state =\u003e ({ count: state.count + 1 })),\n}), {\n  computed: {\n    doubleCount: function() {\n      return this.count * 2\n    },\n    total() {\n      return this.count + this.doubleCount;\n    }\n  },\n  middleware: [log] // \u003c- This is it\n};\n\n```\n\nGreat, now we're outputting the changes to the console. But we need a way to identify the logs when using multiple stores, we could modify the middleware, but... there is another way. 😎\n\n## Log settings (addons.settings)\n\nIn order to turn the logs on we need to add the settings property to the addons object. In the settings object we can set the `name` for the store and `logLevel` to `'diff'` if we want to display only the changes. Or we can use `'all'` in case we want to see the previous state, the changes and the new state. The default value for `logLevel` is `'none'`.\n\n```jsx\nimport create from 'zustand-store-addons';\n\nconst useStore = create((set, get) =\u003e ({\n  count: 0,\n  increment: () =\u003e set(state =\u003e ({ count: state.count + 1 })),\n}), {\n  computed: {\n    doubleCount: function() {\n      return this.count * 2\n    },\n    total() {\n      return this.count + this.doubleCount;\n    }\n  },\n  settings: {\n    name: 'CounterStore',\n    logLevel: 'diff'\n  }\n};\n\n```\n\n### Frequently updated properties\n\nSometimes there are properties that need to be updated very often and logging them constantly can be annoying and potentially fill the console view very quickly. For this cases we can pass a configuration object as a second argument to the `set` and `setState` functions to exclude the operation from logs.\n\n```jsx\nset({ tickerSeconds: 20 }, { excludeFromLogs: true });\n\nuseStore.setState({\n  tickerSeconds: 20,\n  foo: 'bar'\n}, {\n  excludeFromLogs: true\n});\n\n```\n\n### Overwriting state\n\nSince zustand's v3.0.0 we can pass a second argument to the `set` function to replace the state instead of merge it. This can be done in the same way in this package, but if we also need to exclude the operation from logs then the use of an object is required in order to indicate both flags.\n\n```jsx\n\n// This will replace the state\nuseStore.setState({\n  tickerSeconds: 20,\n  foo: 'bar'\n}, true)\n\n// This will replace the state and won't be shown in logs\nuseStore.setState({\n  tickerSeconds: 20,\n  foo: 'bar'\n}, {\n  excludeFromLogs: true,\n  replace: true\n});\n```\n\n## Watchers (addons.watchers)\n\nThis feature allow us to add callbacks directly in our store that will be triggered when a certain property change. In the watchers object we add method definitions matching the method's name to the property we want to watch. The callback will be called passing the `newValue` and `prevValue` as arguments.\n\nLet's return to the example code we've been using.\nWe might want to do something when `total` goes above 20.\n\n```jsx\nimport create from 'zustand-store-addons';\n\nconst useStore = create(\n  // First argument remains the same as zustand's create function\n  (set, get) =\u003e ({\n    count: 0,\n    increment: () =\u003e set(state =\u003e ({ count: state.count + 1 })),\n    above20: false, // \u003c-- We add this property\n  }),\n  // Second argument is were you put the addons\n  {\n    computed: {\n      doubleCount() {\n        return this.count * 2\n      },\n      total() {\n        return this.count + this.doubleCount;\n      }\n    },\n    watchers: {\n      // Will trigger every time \"total\" changes\n      total(newTotal, prevTotal) {\n        // `this` keyword gives us access to set, get and api.\n        if (newTotal \u003e 20 \u0026\u0026 prevTotal \u003c= 20) {\n          this.set({ above20: true })\n        }\n      }\n    },\n    settings: {\n      name: 'CounterStore',\n      logLevel: 'diff'\n    }\n  }\n)\n\n```\n\nThe ***total watcher*** will be trigger every time that `total` property changes and will set `above20` to `true` the first time the value is greater than 20.\n\nInside any watcher function we get access to the `this` keywords which in this case points to an object that contains zustand's  `set` and `get` methods and `api` object.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdiablow%2Fzustand-store-addons","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdiablow%2Fzustand-store-addons","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdiablow%2Fzustand-store-addons/lists"}