{"id":13567114,"url":"https://github.com/udecode/zustand-x","last_synced_at":"2025-05-14T13:09:44.704Z","repository":{"id":41900553,"uuid":"410820077","full_name":"udecode/zustand-x","owner":"udecode","description":"Zustand store factory for a best-in-class developer experience.","archived":false,"fork":false,"pushed_at":"2025-02-15T10:48:40.000Z","size":16753,"stargazers_count":409,"open_issues_count":2,"forks_count":29,"subscribers_count":10,"default_branch":"main","last_synced_at":"2025-05-03T13:43:48.926Z","etag":null,"topics":["state-management","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/udecode.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","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},"funding":{"github":"zbeyens"}},"created_at":"2021-09-27T09:24:27.000Z","updated_at":"2025-04-28T07:05:35.000Z","dependencies_parsed_at":"2024-06-18T13:47:13.836Z","dependency_job_id":"1f52fae2-41d6-4eae-87fd-c27076589606","html_url":"https://github.com/udecode/zustand-x","commit_stats":{"total_commits":117,"total_committers":10,"mean_commits":11.7,"dds":0.5470085470085471,"last_synced_commit":"90548127cbe788ad6baa9fcd22260de064beb052"},"previous_names":["udecode/zustand-x","udecode/zustood"],"tags_count":27,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/udecode%2Fzustand-x","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/udecode%2Fzustand-x/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/udecode%2Fzustand-x/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/udecode%2Fzustand-x/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/udecode","download_url":"https://codeload.github.com/udecode/zustand-x/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254131534,"owners_count":22019946,"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":["state-management","zustand"],"created_at":"2024-08-01T13:02:24.027Z","updated_at":"2025-05-14T13:09:39.685Z","avatar_url":"https://github.com/udecode.png","language":"TypeScript","funding_links":["https://github.com/sponsors/zbeyens"],"categories":["TypeScript","others"],"sub_categories":[],"readme":"# Zustand X\n\nAn extension for [Zustand](https://github.com/pmndrs/zustand) that auto-generates type-safe actions, selectors, and hooks for your state. Built with TypeScript and React in mind.\n\n## Features\n\n- Auto-generated type-safe hooks for each state field\n- Simple patterns: `store.get('name')` and `store.set('name', value)`\n- Extend your store with computed values using `extendSelectors`\n- Add reusable actions with `extendActions`\n- Built-in support for devtools, persist, immer, and mutative\n\n## Why\n\nBuilt on top of `zustand`, `zustand-x` offers a better developer experience with less boilerplate. Create and interact with stores faster using a more intuitive API.\n\n\u003e Looking for React Context-based state management instead of global state? Check out [Jotai X](https://github.com/udecode/jotai-x) - same API, different state model.\n\n## Installation\n\n```bash\npnpm add zustand-x\n```\n\nYou'll also need `react` and [`zustand`](https://github.com/pmndrs/zustand) installed.\n\n## Quick Start\n\nHere's how to create a simple store:\n\n```tsx\nimport { createStore, useStoreState, useStoreValue } from 'zustand-x';\n\n// Create a store with an initial state\nconst repoStore = createStore({\n  name: 'ZustandX',\n  stars: 0,\n});\n\n// Use it in your components\nfunction RepoInfo() {\n  const name = useStoreValue(repoStore, 'name');\n  const stars = useStoreValue(repoStore, 'stars');\n\n  return (\n    \u003cdiv\u003e\n      \u003ch1\u003e{name}\u003c/h1\u003e\n      \u003cp\u003e{stars} stars\u003c/p\u003e\n    \u003c/div\u003e\n  );\n}\n\nfunction AddStarButton() {\n  const [, setStars] = useStoreState(repoStore, 'stars');\n  return \u003cbutton onClick={() =\u003e setStars((s) =\u003e s + 1)}\u003eAdd star\u003c/button\u003e;\n}\n```\n\n## Core Concepts\n\n### Store Configuration\n\nThe store is where everything begins. Configure it with type-safe middleware:\n\n```ts\nimport { createStore } from 'zustand-x';\n\n// Types are inferred, including middleware options\nconst userStore = createStore(\n  {\n    name: 'Alice',\n    loggedIn: false,\n  },\n  {\n    name: 'user',\n    devtools: true, // Enable Redux DevTools\n    persist: true, // Persist to localStorage\n    mutative: true, // Enable immer-style mutations\n  }\n);\n```\n\nAvailable middleware options:\n\n```ts\n{\n  name: string;\n  devtools?: boolean | DevToolsOptions;\n  persist?: boolean | PersistOptions;\n  immer?: boolean | ImmerOptions;\n  mutative?: boolean | MutativeOptions;\n}\n```\n\n### Reading and Writing State\n\nThe API is designed to be intuitive. Here's how you work with state:\n\n#### Reading State\n\n```ts\n// Get a single value\nstore.get('name'); // =\u003e 'Alice'\n\n// Get the entire state\nstore.get('state');\n\n// Call a selector with arguments\nstore.get('someSelector', 1, 2);\n```\n\n#### Writing State\n\n```ts\n// Set a single value\nstore.set('name', 'Bob');\n\n// Call an action\nstore.set('someAction', 10);\n\n// Update multiple values at once\nstore.set('state', (draft) =\u003e {\n  draft.name = 'Bob';\n  draft.loggedIn = true;\n});\n```\n\n### Subscribing State\n```ts\n// Subscribe to changes\nconst unsubscribe = store.subscribe('name', (name, previousName) =\u003e {\n  console.log('Name changed from', previousName, 'to', name);\n});\n\n// Subscribe to the entire state\nconst unsubscribe = store.subscribe('state', (state) =\u003e {\n  console.log('State changed:', state);\n});\n\n// Subscribe to a selector with arguments\nconst unsubscribe = store.subscribe('someSelector', 1, 2, (result) =\u003e {\n  console.log('Selector result changed:', result);\n});\n\n// Subscribe with an additional selector and options\nconst unsubscribe = store.subscribe(\n  'name',\n  name =\u003e name.length,\n  length =\u003e console.log('Name length changed:', length),\n  { fireImmediately: true } // Fire the callback immediately when subscribing\n);\n```\n\n### React Hooks\n\n#### `useStoreValue(store, key, ...args)`\n\nSubscribe to a single value or selector. Optionally pass an equality function for custom comparison:\n\n```ts\nconst name = useStoreValue(store, 'name');\n\n// With selector arguments\nconst greeting = useStoreValue(store, 'greeting', 'Hello');\n\n// With custom equality function for arrays/objects\nconst items = useStoreValue(\n  store,\n  'items',\n  (a, b) =\u003e a.length === b.length \u0026\u0026 a.every((item, i) =\u003e item.id === b[i].id)\n);\n```\n\n#### `useStoreState(store, key, [equalityFn])`\n\nGet a value and its setter, just like `useState`. Perfect for form inputs:\n\n```ts\nfunction UserForm() {\n  const [name, setName] = useStoreState(store, 'name');\n  const [email, setEmail] = useStoreState(store, 'email');\n\n  return (\n    \u003cform\u003e\n      \u003cinput value={name} onChange={(e) =\u003e setName(e.target.value)} /\u003e\n      \u003cinput value={email} onChange={(e) =\u003e setEmail(e.target.value)} /\u003e\n    \u003c/form\u003e\n  );\n}\n```\n\n#### `useTracked(store, key)`\n\nSubscribe to a value with minimal re-renders. Perfect for large objects where you only use a few fields:\n\n```ts\nfunction UserEmail() {\n  // Only re-renders when user.email changes\n  const user = useTracked(store, 'user');\n  return \u003cdiv\u003e{user.email}\u003c/div\u003e;\n}\n\nfunction UserAvatar() {\n  // Only re-renders when user.avatar changes\n  const user = useTracked(store, 'user');\n  return \u003cimg src={user.avatar} /\u003e;\n}\n```\n\n#### `useTrackedStore(store)`\n\nGet the entire store with tracking.\n\n```ts\nfunction UserProfile() {\n  // Only re-renders when accessed fields change\n  const state = useTrackedStore(store);\n\n  return (\n    \u003cdiv\u003e\n      \u003ch1\u003e{state.user.name}\u003c/h1\u003e\n      \u003cp\u003e{state.user.bio}\u003c/p\u003e\n      {state.isAdmin \u0026\u0026 \u003cAdminPanel /\u003e}\n    \u003c/div\u003e\n  );\n}\n```\n\n### Extending Your Store\n\n#### Adding Selectors\n\nSelectors help you derive new values from your state. Chain them together to build complex computations:\n\n```ts\nconst store = createStore(\n  { firstName: 'Jane', lastName: 'Doe' },\n  { mutative: true }\n);\n\nconst extendedStore = store\n  .extendSelectors(({ get }) =\u003e ({\n    fullName: () =\u003e get('firstName') + ' ' + get('lastName'),\n  }))\n  .extendSelectors(({ get }) =\u003e ({\n    fancyTitle: (prefix: string) =\u003e prefix + get('fullName').toUpperCase(),\n  }));\n\n// Using them\nextendedStore.get('fullName'); // =\u003e 'Jane Doe'\nextendedStore.get('fancyTitle', 'Hello '); // =\u003e 'Hello JANE DOE'\n```\n\nUse them in components:\n\n```ts\nfunction Title() {\n  const fancyTitle = useStoreValue(extendedStore, 'fancyTitle', 'Welcome ')\n  return \u003ch1\u003e{fancyTitle}\u003c/h1\u003e\n}\n```\n\n#### Adding Actions\n\nActions are functions that modify state. They can read or write state and even compose with other actions:\n\n```ts\nconst storeWithActions = store.extendActions(\n  ({ get, set, actions: { someActionToOverride } }) =\u003e ({\n    updateName: (newName: string) =\u003e set('name', newName),\n    resetState: () =\u003e {\n      set('state', (draft) =\u003e {\n        draft.firstName = 'Jane';\n        draft.lastName = 'Doe';\n      });\n    },\n    someActionToOverride: () =\u003e {\n      // You could call the original if you want:\n      // someActionToOverride()\n      // then do more stuff...\n    },\n  })\n);\n\n// Using actions\nstoreWithActions.set('updateName', 'Julia');\nstoreWithActions.set('resetState');\n```\n\n### Middleware Configuration\n\nEach middleware can be enabled with a simple boolean or configured with options:\n\n```ts\nconst store = createStore(\n  { name: 'ZustandX', stars: 10 },\n  {\n    name: 'repo',\n    devtools: { enabled: true }, // Redux DevTools with options\n    persist: { enabled: true }, // localStorage with options\n    mutative: true, // shorthand for { enabled: true }\n  }\n);\n```\n\n### Zustand Store\n\nAccess the underlying Zustand store when needed:\n\n```ts\n// Use the original Zustand hook\nconst name = useStoreSelect(store, (state) =\u003e state.name);\n\n// Get the vanilla store\nconst vanillaStore = store.store;\nvanillaStore.getState();\nvanillaStore.setState({ count: 1 });\n\n// Subscribe to changes\nconst unsubscribe = vanillaStore.subscribe((state) =\u003e\n  console.log('New state:', state)\n);\n```\n\n## Comparison with Zustand\n\n```ts\n// zustand\nimport create from 'zustand'\n\nconst useStore = create((set, get) =\u003e ({\n  count: 0,\n  increment: () =\u003e set((state) =\u003e ({ count: state.count + 1 })),\n  // Computed values need manual memoization\n  double: 0,\n  setDouble: () =\u003e set((state) =\u003e ({ double: state.count * 2 }))\n}))\n\n// Component\nconst count = useStore((state) =\u003e state.count)\nconst increment = useStore((state) =\u003e state.increment)\nconst double = useStore((state) =\u003e state.double)\n\n// zustand-x\nimport { createStore, useStoreValue, useStoreState } from 'zustand-x'\n\nconst store = createStore({ count: 0 })\n  .extendSelectors(({ get }) =\u003e ({\n    // Computed values are auto-memoized\n    double: () =\u003e get('count') * 2\n  }))\n  .extendActions(({ set }) =\u003e ({\n    increment: () =\u003e set('count', (count) =\u003e count + 1),\n  }))\n\n// Component\nconst count = useStoreValue(store, 'count')\nconst double = useStoreValue(store, 'double')\nconst increment = () =\u003e store.set('increment')\n```\n\nKey differences:\n\n- No need to create selectors manually - they're auto-generated for each state field\n- Direct access to state fields without selector functions\n- Simpler action definitions with `set('key', value)` pattern\n- Type-safe by default without extra type annotations\n- Computed values are easier to define and auto-memoized with `extendSelectors`\n\n## Migration to v6\n\n```ts\n// Before\nstore.use.name();\nstore.get.name();\nstore.set.name('Bob');\n\n// Now\nuseStoreValue(store, 'name');\nstore.get('name');\nstore.set('name', 'Bob');\n\n// With selectors and actions\n// Before\nstore.use.someSelector(42);\nstore.set.someAction(10);\n\n// Now\nuseStoreValue(store, 'someSelector', 42);\nstore.set('someAction', 10);\n```\n\n## License\n\n[MIT](./LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fudecode%2Fzustand-x","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fudecode%2Fzustand-x","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fudecode%2Fzustand-x/lists"}