{"id":13595033,"url":"https://github.com/xoidlabs/xoid","last_synced_at":"2025-12-05T11:02:14.634Z","repository":{"id":46973024,"uuid":"300859216","full_name":"xoidlabs/xoid","owner":"xoidlabs","description":"Framework-agnostic state management library designed for simplicity and scalability ⚛","archived":false,"fork":false,"pushed_at":"2024-09-18T15:22:33.000Z","size":5031,"stargazers_count":162,"open_issues_count":2,"forks_count":6,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-04-01T22:16:35.594Z","etag":null,"topics":["framework-agnostic","javascript","preact","react","ssr","state-machine","state-management","svelte","typescript","vanilla","vue"],"latest_commit_sha":null,"homepage":"https://xoid.dev","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/xoidlabs.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","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},"funding":{"open_collective":"xoid"}},"created_at":"2020-10-03T11:00:08.000Z","updated_at":"2025-02-11T01:16:30.000Z","dependencies_parsed_at":"2023-02-18T16:01:44.270Z","dependency_job_id":"e1753f1f-9332-4828-ac32-02c362af54f3","html_url":"https://github.com/xoidlabs/xoid","commit_stats":{"total_commits":619,"total_committers":40,"mean_commits":15.475,"dds":0.5218093699515347,"last_synced_commit":"d4679b6e96d49f4777757766281962df50a7cccf"},"previous_names":["xoidlabs/xoid","onurkerimov/xoid"],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xoidlabs%2Fxoid","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xoidlabs%2Fxoid/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xoidlabs%2Fxoid/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xoidlabs%2Fxoid/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/xoidlabs","download_url":"https://codeload.github.com/xoidlabs/xoid/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247369952,"owners_count":20927928,"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":["framework-agnostic","javascript","preact","react","ssr","state-machine","state-management","svelte","typescript","vanilla","vue"],"created_at":"2024-08-01T16:01:42.771Z","updated_at":"2025-12-05T11:02:14.547Z","avatar_url":"https://github.com/xoidlabs.png","language":"TypeScript","funding_links":["https://opencollective.com/xoid"],"categories":["TypeScript"],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://xoid.dev\"\u003e\n    \u003cimg width=\"560\" src=\"https://raw.githubusercontent.com/xoidlabs/xoid/master/assets/logo-full.svg\" /\u003e\n  \u003c/a\u003e\n\u003c/p\u003e \n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"#-react\"\u003e\n    \u003cimg src=\"https://raw.githubusercontent.com/xoidlabs/xoid/master/assets/integrations/react.ico\" width=\"14\"/\u003e\n    React\n  \u003c/a\u003e\u0026nbsp;\u0026nbsp;\n  \u003ca href=\"#-vue\"\u003e\n    \u003cimg src=\"https://raw.githubusercontent.com/xoidlabs/xoid/master/assets/integrations/vue.png\" width=\"14\"/\u003e\n    Vue\n  \u003c/a\u003e\u0026nbsp;\u0026nbsp;\n  \u003ca href=\"#-svelte\"\u003e\n    \u003cimg src=\"https://raw.githubusercontent.com/xoidlabs/xoid/master/assets/integrations/svelte.png\" width=\"14\"/\u003e\n    Svelte\n  \u003c/a\u003e\u0026nbsp;\u0026nbsp;\n  \u003ca href=\"#subscriptions\"\u003e\n    \u003cimg src=\"https://raw.githubusercontent.com/xoidlabs/xoid/master/assets/integrations/js.png\" width=\"14\"/\u003e\n    Vanilla JS\n  \u003c/a\u003e\u0026nbsp;\u0026nbsp;\n  \u003ca href=\"#redux-devtools\"\u003e\n    \u003cimg src=\"https://raw.githubusercontent.com/xoidlabs/xoid/master/assets/integrations/redux.svg\" width=\"14\"/\u003e\n    Redux Devtools\n  \u003c/a\u003e\n\u003c/p\u003e\n\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://bundlephobia.com/result?p=xoid\" \u003e\n    \u003cimg alt=\"Bundle Size\" src=\"https://deno.bundlejs.com/badge?q=xoid@1.0.0-beta.9\u0026treeshake=[*]\u0026\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://www.npmjs.com/package/xoid\"\u003e\n    \u003cimg alt=\"Version\" src=\"https://img.shields.io/npm/v/xoid?style=flat\u00264f2eb3=293140\u0026colorA=4f2eb3\u0026colorB=4f2eb3\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://www.npmjs.com/package/xoid\"\u003e\n    \u003cimg alt=\"Downloads\" src=\"https://img.shields.io/npm/dt/xoid.svg?style=flat\u0026colorA=4f2eb3\u0026colorB=4f2eb3\"/\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://www.npmjs.com/package/xoid\"\u003e\n    \u003cimg alt=\"License\" src=\"https://img.shields.io/github/license/xoidlabs/xoid?style=flat\u00264f2eb3=293140\u0026colorA=4f2eb3\u0026colorB=4f2eb3\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n**xoid** is a framework-agnostic state management library. \n**X** in its name is an ode to great projects such as Redu**X**, Mob**X** and **X**state. \nIt's the result of careful analyses of different state management tools and paradigms. \nIt was designed to be tiny (~1kB gzipped) and easy-to-learn.\n\nThe biggest aim of **xoid** is to unify global state, local component state, finite state machines, and observable streams in the same API. This is especially a big deal for React users where switching between local and global state requires thinking in two different APIs.\nIt might be the very first library to introduce the notion of [isomorphic component logic](#-isomorphic-component-logic) that's able to run across multiple frameworks. \nWith **xoid**, you can move business logic out of components in a **truly** framework-agnostic manner.\n\n**xoid** (*zoid* is easier to say multiple times) is a robust library based on explicit subscriptions, immutable updates, and a first-class TypeScript support. This makes it ideal for teams. If you prefer implicit subscriptions and mutable updates similar to MobX or Vue 3, you can use **@xoid/reactive**, a tiny proxy-state layer over **xoid**. More features are explained below, and the [documentation website](https://xoid.dev).\n\nTo install, run the following command:\n\n```bash\nnpm install xoid\n```\n\n---\n\n\u003ch3 align=\"center\"\u003e\nVisit \u003ca href=\"https://xoid.dev\"\u003exoid.dev\u003c/a\u003e for detailed docs and recipes.\n\u003c/h3\u003e\n\n---\n\n## Examples\n\n- [Counter](https://github.com/xoidlabs/xoid/blob/master/examples/counter) [![Open in CodeSandbox](https://img.shields.io/badge/Open%20in-CodeSandbox-blue?style=flat\u0026colorA=4f2eb3\u0026colorB=4f2eb3\u0026logo=codesandbox)](https://githubbox.com/xoidlabs/xoid/tree/master/examples/counter)\n\n- [Todos (Basic)](https://github.com/xoidlabs/xoid/blob/master/examples/todos-basic) [![Open in CodeSandbox](https://img.shields.io/badge/Open%20in-CodeSandbox-blue?style=flat\u0026colorA=4f2eb3\u0026colorB=4f2eb3\u0026logo=codesandbox)](https://githubbox.com/xoidlabs/xoid/tree/master/examples/todos-basic)\n\n- [Todos (Filtered)](https://github.com/xoidlabs/xoid/blob/master/examples/todos-filtered) [![Open in CodeSandbox](https://img.shields.io/badge/Open%20in-CodeSandbox-blue?style=flat\u0026colorA=4f2eb3\u0026colorB=4f2eb3\u0026logo=codesandbox)](https://githubbox.com/xoidlabs/xoid/tree/master/examples/todos-filtered)\n\n- [Celcius-Fahrenheit conversion](https://github.com/xoidlabs/xoid/blob/master/examples/celcius-fahrenheit) [![Open in CodeSandbox](https://img.shields.io/badge/Open%20in-CodeSandbox-blue?style=flat\u0026colorA=4f2eb3\u0026colorB=4f2eb3\u0026logo=codesandbox)](https://githubbox.com/xoidlabs/xoid/tree/master/examples/celcius-fahrenheit)\n\n- [Finite state stopwatch](https://github.com/xoidlabs/xoid/blob/master/examples/finite-state-stopwatch) [![Open in CodeSandbox](https://img.shields.io/badge/Open%20in-CodeSandbox-blue?style=flat\u0026colorA=4f2eb3\u0026colorB=4f2eb3\u0026logo=codesandbox)](https://githubbox.com/xoidlabs/xoid/tree/master/examples/finite-state-stopwatch)\n\n- [Dots and arrows](https://githubbox.com/xoidlabs/xoid/tree/master/examples/dots-and-arrows) [![Open in CodeSandbox](https://img.shields.io/badge/Open%20in-CodeSandbox-blue?style=flat\u0026colorA=4f2eb3\u0026colorB=4f2eb3\u0026logo=codesandbox)](https://githubbox.com/xoidlabs/xoid/tree/master/examples/dots-and-arrows)\n\n- [Transient update resize observer](https://github.com/xoidlabs/xoid/blob/master/examples/transient-update-resize-observer) [![Open in CodeSandbox](https://img.shields.io/badge/Open%20in-CodeSandbox-blue?style=flat\u0026colorA=4f2eb3\u0026colorB=4f2eb3\u0026logo=codesandbox)](https://githubbox.com/xoidlabs/xoid/tree/master/examples/transient-update-resize-observer)\n\n- [Redux Devtools](https://github.com/xoidlabs/xoid/blob/master/examples/redux-devtools) [![Open in CodeSandbox](https://img.shields.io/badge/Open%20in-CodeSandbox-blue?style=flat\u0026colorA=4f2eb3\u0026colorB=4f2eb3\u0026logo=codesandbox)](https://githubbox.com/xoidlabs/xoid/tree/master/examples/redux-devtools)\n\n\n- [xoid vs useReducer vs useMethods](https://githubbox.com/xoidlabs/xoid/tree/master/examples/xoid-vs-usereducer-vs-usemethods) [![Open in CodeSandbox](https://img.shields.io/badge/Open%20in-CodeSandbox-blue?style=flat\u0026colorA=4f2eb3\u0026colorB=4f2eb3\u0026logo=codesandbox)](https://githubbox.com/xoidlabs/xoid/tree/master/examples/xoid-vs-usereducer-vs-usemethods)\n\n\n\n## Quick Tutorial\n\n\u003e Basic usage of **xoid** can be learned within a few minutes.\n\n### Atom\n\nAtoms are holders of state.\n\n```js\nimport { atom } from 'xoid'\n\nconst $count = atom(3)\nconsole.log($count.value) // 3\n$count.set(5)\n$count.update((state) =\u003e state + 1)\nconsole.log($count.value) // 6\n```\n\nAtoms can have actions.\n\n```js\nimport { atom } from 'xoid'\n\nconst $count = atom(5, (a) =\u003e ({\n  increment: () =\u003e a.update(s =\u003e s + 1),\n  decrement: () =\u003e a.value-- // `.value` setter is supported too\n}))\n\n$count.actions.increment()\n```\n\nThere's the `.focus` method, which can be used as a selector/lens. **xoid** is based on immutable updates, so if you \"surgically\" set state of a focused branch, changes will propagate to the root.\n\n```js\nimport { atom } from 'xoid'\n\nconst $atom = atom({ deeply: { nested: { alpha: 5 } } })\nconst previousValue = $atom.value\n\n// select `.deeply.nested.alpha`\nconst $alpha = $atom.focus(s =\u003e s.deeply.nested.alpha)\n$alpha.set(6)\n\n// root state is replaced with new immutable state\nassert($atom.value !== previousValue) // ✅\nassert($atom.value.deeply.nested.alpha === 6) // ✅\n```\n\n\n### Derived state\n\nState can be derived from other atoms. This API was heavily inspired by **Recoil**.\n\n```js\nconst $alpha = atom(3)\nconst $beta = atom(5)\n// derived atom\nconst $sum = atom((read) =\u003e read($alpha) + read($beta))\n```\n\nAlternatively, `.map` method can be used to quickly derive the state from a single atom.\n\n```js\nconst $alpha = atom(3)\n// derived atom\nconst $doubleAlpha = $alpha.map((s) =\u003e s * 2)\n```\n\u003e Atoms are lazily evaluated. This means that the callback functions of `$sum` and `$doubleAlpha` in this example won't execute until the first subscription to these atoms. This is a performance optimization.\n\n### Subscriptions\n\nFor subscriptions, `subscribe` and `watch` are used. They are the same, except `watch` runs the callback immediately, while `subscribe` waits for the first update after subscription.\n\n```js\nconst unsub = $atom.subscribe((state, previousState) =\u003e {\n  console.log(state, previousState)\n})\n\n// later\nunsub()\n```\n\u003e This concludes the basic usage! 🎉\n\n## Framework Integrations\n\nIntegrating with frameworks is so simple. No configuration, or context providers are needed. Currently all `@xoid/react`, `@xoid/vue`, and `@xoid/svelte` packages export a hook called `useAtom`. \n\n### \u003cimg src=\"https://raw.githubusercontent.com/xoidlabs/xoid/master/assets/integrations/react.ico\" width=\"16\"/\u003e React\n\n\n```js\nimport { useAtom } from '@xoid/react'\n\n// in a React component\nconst state = useAtom(atom)\n```\n\n### \u003cimg src=\"https://raw.githubusercontent.com/xoidlabs/xoid/master/assets/integrations/vue.png\" width=\"16\"/\u003e Vue\n\n```html\n\u003cscript setup\u003e\nimport { useAtom } from '@xoid/vue'\n\nconst value = useAtom(myAtom)\n\u003c/script\u003e\n\n\u003ctemplate\u003e\n  \u003cdiv\u003e{{ value }}\u003c/div\u003e\n\u003c/template\u003e\n```\n\n\n### \u003cimg src=\"https://raw.githubusercontent.com/xoidlabs/xoid/master/assets/integrations/svelte.png\" width=\"16\"/\u003e Svelte\n\n```html\n\u003cscript\u003e\nimport { useAtom } from '@xoid/svelte'\n\nlet atom = useAtom(myAtom)\n\u003c/script\u003e\n\n\u003cheader\u003e{$atom}\u003c/header\u003e\n```\n\n## 🔥 Isomorphic component logic\n\nThis might be the most unique feature of **xoid**. With **xoid**, you can write component logic (including lifecycle) ONCE, and run it across multiple frameworks. This feature is for you especially if:\n- You're a design system, or a headless UI library maintainer\n- You're using multiple frameworks in your project, or refactoring your code from one framework to another\n- You dislike React's render cycle and want a simpler, real closure for managing complex state\n\nThe following is called a \"setup\" function:\n\n```js\nimport { atom, Atom, effect, inject } from 'xoid'\nimport { ThemeSymbol } from './theme'\n\nexport const CounterSetup = ($props: Atom\u003c{ initialValue: number }\u003e) =\u003e {\n  const { initialValue } = $props.value\n\n  const $counter = atom(initialValue)\n  const increment = () =\u003e $counter.update((s) =\u003e s + 1)\n  const decrement = () =\u003e $counter.update((s) =\u003e s - 1)\n\n  effect(() =\u003e {\n    console.log('mounted')\n    return () =\u003e console.log('unmounted')\n  })\n\n  const theme = inject(ThemeSymbol)\n  console.log(\"theme is obtained using context:\", theme)\n\n  return { $counter, increment, decrement }\n}\n```\nAll `@xoid/react`, `@xoid/vue`, and `@xoid/svelte` modules have an isomorphic `useSetup` function that can consume functions like this. \n\n\u003e We're aware that not all users need this feature, so we've built it tree-shakable. If `useAtom` is all you need, you may choose to import it from `'@xoid/[FRAMEWORK]/useAtom'`. \n\n\nWith this feature, you can effectively replace the following framework-specific APIs:\n\n|  | \u003cimg src=\"https://raw.githubusercontent.com/xoidlabs/xoid/master/assets/logo-plain.svg\" width=\"16\"/\u003e xoid | \u003cimg src=\"https://raw.githubusercontent.com/xoidlabs/xoid/master/assets/integrations/react.ico\" width=\"16\"/\u003e React | \u003cimg src=\"https://raw.githubusercontent.com/xoidlabs/xoid/master/assets/integrations/vue.png\" width=\"16\"/\u003e Vue | \u003cimg src=\"https://raw.githubusercontent.com/xoidlabs/xoid/master/assets/integrations/svelte.png\" width=\"16\"/\u003e Svelte |\n|---|---|---|---|---|\n| State | `create` | `useState` / `useReducer` | `reactive` / `shallowRef` | `readable` / `writable` |\n| Derived state | `create` | `useMemo` | `computed` | `derived` |\n| Lifecycle | `effect` | `useEffect` | `onMounted`, `onUnmounted` | `onMount`, `onDestroy` |\n| Dependency injection | `inject` | `useContext` | `inject` | `getContext` |\n\n\n## Redux Devtools\n\nImport `@xoid/devtools` and set a `debugValue` to your atom. It will send values and action names to the Redux Devtools Extension.\n\n```js\nimport devtools from '@xoid/devtools'\nimport create from 'xoid'\ndevtools() // run once\n\nconst $atom = atom(\n  { alpha: 5 }, \n  ($atom) =\u003e {\n    const $alpha = $atom.focus(s =\u003e s.alpha)\n    return {\n      inc: () =\u003e $alpha.update(s =\u003e s + 1),\n      deeply: { nested: { action: () =\u003e $alpha.update((s) =\u003e s + 1) } }\n    }\n  }\n)\n\natom.debugValue = 'myAtom' // enable watching it by the devtools\n\nconst { deeply, inc } = atom.actions\ninc() // \"(myAtom).inc\"\ndeeply.nested.action() // \"(myAtom).deeply.nested.action\"\natom.focus(s =\u003e s.alpha).set(25)  // \"(myAtom) Update ([timestamp])\n```\n\n## Finite state machines\n\nNo additional syntax is required for state machines. Just use the `create` function.\n\n```js\nimport { atom } from 'xoid'\nimport { useAtom } from '@xoid/react'\n\nconst createMachine = () =\u003e {\n  const red = { color: '#f00', onClick: () =\u003e atom.set(green) }\n  const green = { color: '#0f0', onClick: () =\u003e atom.set(red) }\n  return atom(red)\n}\n\n// in a React component\nconst { color, onClick } = useAtom(createMachine)\nreturn \u003cdiv style={{ color }} onClick={onClick} /\u003e\n```\n\n---\n\nIf you've read until here, you have enough knowledge to start using **xoid**. You can refer to the [documentation website](https://xoid.dev) for more.\n\n\n## Why **xoid**?\n\n\u003cimg align=\"right\" width=\"50%\" src=\"https://raw.githubusercontent.com/xoidlabs/xoid/master/assets/diagram.png\" alt=\"Venn diagram that shows that xoid is able to unify global, local state, and finite state machines across React, Vue, Svelte, and vanilla JS\"\u003e\n\n- Easy to learn\n- Small bundle size\n- Framework-agnostic\n- No middlewares needed\n- First-class Typescript support\n- Easy to work with nested states\n- Computed values, transient updates\n- Same API to rule them all!\n  - Global state, Local state, FSMs, Streams\n  - React, Vue, Svelte, Vanilla JavaScript\n\n## Packages\n\n- `xoid` - Core package\n- `@xoid/react` - **React** integration\n- `@xoid/vue` - **Vue** integration\n- `@xoid/svelte` - **Svelte** integration\n- `@xoid/devtools` - **Redux Devtools** integration\n- `@xoid/reactive` - MobX-like proxy state library over **xoid**\n\n\n## Thanks\n\nThis repo initially started as a fork of [zustand](https://github.com/pmndrs/zustand). Due to this, GitHub's \"Contributors\" section can be misleading. Majority of the people on that list are actually Zustand's contributors until September 2020.\n\nFollowing awesome projects inspired **xoid** a lot.\n- [Recoil](https://github.com/facebookexperimental/Recoil)\n- [zustand](https://github.com/pmndrs/zustand)\n- [mobx-state-tree](https://github.com/mobxjs/mobx-state-tree)\n\nThanks to [Anatoly](http://a-maslennikov.com/) for the pencil\u0026ruler icon [#24975](https://www.flaticon.com/free-icon/ruler_245975).\n\n---\n\nIf you'd like to support the project, consider sponsoring on OpenCollective:\n\n\u003ca href=\"https://opencollective.com/xoid\"\u003e\n  \u003cimg src=\"https://opencollective.com/xoid/tiers/fan.svg\" /\u003e\n\u003c/a\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxoidlabs%2Fxoid","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fxoidlabs%2Fxoid","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxoidlabs%2Fxoid/lists"}