Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/xoidlabs/xoid
Framework-agnostic state management library designed for simplicity and scalability ⚛
https://github.com/xoidlabs/xoid
framework-agnostic javascript preact react ssr state-machine state-management svelte typescript vanilla vue
Last synced: 5 days ago
JSON representation
Framework-agnostic state management library designed for simplicity and scalability ⚛
- Host: GitHub
- URL: https://github.com/xoidlabs/xoid
- Owner: xoidlabs
- License: mit
- Created: 2020-10-03T11:00:08.000Z (over 4 years ago)
- Default Branch: main
- Last Pushed: 2024-09-18T15:22:33.000Z (4 months ago)
- Last Synced: 2025-01-11T13:06:59.112Z (12 days ago)
- Topics: framework-agnostic, javascript, preact, react, ssr, state-machine, state-management, svelte, typescript, vanilla, vue
- Language: TypeScript
- Homepage: https://xoid.dev
- Size: 4.8 MB
- Stars: 162
- Watchers: 5
- Forks: 7
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Funding: .github/FUNDING.yml
- License: LICENSE
Awesome Lists containing this project
README
React
Vue
Svelte
Vanilla JS
Redux Devtools
**xoid** is a framework-agnostic state management library.
**X** in its name is an ode to great projects such as Redu**X**, Mob**X** and **X**state.
It's the result of careful analyses of different state management tools and paradigms.
It was designed to be tiny (~1kB gzipped) and easy-to-learn.The 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.
It 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.
With **xoid**, you can move business logic out of components in a **truly** framework-agnostic manner.**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).
To install, run the following command:
```bash
npm install xoid
```---
Visit xoid.dev for detailed docs and recipes.---
## Examples
- [Counter](https://github.com/xoidlabs/xoid/blob/master/examples/counter) [![Open in CodeSandbox](https://img.shields.io/badge/Open%20in-CodeSandbox-blue?style=flat&colorA=4f2eb3&colorB=4f2eb3&logo=codesandbox)](https://githubbox.com/xoidlabs/xoid/tree/master/examples/counter)
- [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&colorA=4f2eb3&colorB=4f2eb3&logo=codesandbox)](https://githubbox.com/xoidlabs/xoid/tree/master/examples/todos-basic)
- [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&colorA=4f2eb3&colorB=4f2eb3&logo=codesandbox)](https://githubbox.com/xoidlabs/xoid/tree/master/examples/todos-filtered)
- [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&colorA=4f2eb3&colorB=4f2eb3&logo=codesandbox)](https://githubbox.com/xoidlabs/xoid/tree/master/examples/celcius-fahrenheit)
- [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&colorA=4f2eb3&colorB=4f2eb3&logo=codesandbox)](https://githubbox.com/xoidlabs/xoid/tree/master/examples/finite-state-stopwatch)
- [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&colorA=4f2eb3&colorB=4f2eb3&logo=codesandbox)](https://githubbox.com/xoidlabs/xoid/tree/master/examples/dots-and-arrows)
- [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&colorA=4f2eb3&colorB=4f2eb3&logo=codesandbox)](https://githubbox.com/xoidlabs/xoid/tree/master/examples/transient-update-resize-observer)
- [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&colorA=4f2eb3&colorB=4f2eb3&logo=codesandbox)](https://githubbox.com/xoidlabs/xoid/tree/master/examples/redux-devtools)
- [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&colorA=4f2eb3&colorB=4f2eb3&logo=codesandbox)](https://githubbox.com/xoidlabs/xoid/tree/master/examples/xoid-vs-usereducer-vs-usemethods)
## Quick Tutorial
> Basic usage of **xoid** can be learned within a few minutes.
### Atom
Atoms are holders of state.
```js
import { atom } from 'xoid'const $count = atom(3)
console.log($count.value) // 3
$count.set(5)
$count.update((state) => state + 1)
console.log($count.value) // 6
```Atoms can have actions.
```js
import { atom } from 'xoid'const $count = atom(5, (a) => ({
increment: () => a.update(s => s + 1),
decrement: () => a.value-- // `.value` setter is supported too
}))$count.actions.increment()
```There'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.
```js
import { atom } from 'xoid'const $atom = atom({ deeply: { nested: { alpha: 5 } } })
const previousValue = $atom.value// select `.deeply.nested.alpha`
const $alpha = $atom.focus(s => s.deeply.nested.alpha)
$alpha.set(6)// root state is replaced with new immutable state
assert($atom.value !== previousValue) // ✅
assert($atom.value.deeply.nested.alpha === 6) // ✅
```### Derived state
State can be derived from other atoms. This API was heavily inspired by **Recoil**.
```js
const $alpha = atom(3)
const $beta = atom(5)
// derived atom
const $sum = atom((read) => read($alpha) + read($beta))
```Alternatively, `.map` method can be used to quickly derive the state from a single atom.
```js
const $alpha = atom(3)
// derived atom
const $doubleAlpha = $alpha.map((s) => s * 2)
```
> 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.### Subscriptions
For 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.
```js
const unsub = $atom.subscribe((state, previousState) => {
console.log(state, previousState)
})// later
unsub()
```
> This concludes the basic usage! 🎉## Framework Integrations
Integrating 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`.
### React
```js
import { useAtom } from '@xoid/react'// in a React component
const state = useAtom(atom)
```### Vue
```html
import { useAtom } from '@xoid/vue'
const value = useAtom(myAtom)
{{ value }}```
### Svelte
```html
import { useAtom } from '@xoid/svelte'
let atom = useAtom(myAtom)
{$atom}
```## 🔥 Isomorphic component logic
This 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:
- You're a design system, or a headless UI library maintainer
- You're using multiple frameworks in your project, or refactoring your code from one framework to another
- You dislike React's render cycle and want a simpler, real closure for managing complex stateThe following is called a "setup" function:
```js
import { atom, Atom, effect, inject } from 'xoid'
import { ThemeSymbol } from './theme'export const CounterSetup = ($props: Atom<{ initialValue: number }>) => {
const { initialValue } = $props.valueconst $counter = atom(initialValue)
const increment = () => $counter.update((s) => s + 1)
const decrement = () => $counter.update((s) => s - 1)effect(() => {
console.log('mounted')
return () => console.log('unmounted')
})const theme = inject(ThemeSymbol)
console.log("theme is obtained using context:", theme)return { $counter, increment, decrement }
}
```
All `@xoid/react`, `@xoid/vue`, and `@xoid/svelte` modules have an isomorphic `useSetup` function that can consume functions like this.> 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'`.
With this feature, you can effectively replace the following framework-specific APIs:
| | xoid | React | Vue | Svelte |
|---|---|---|---|---|
| State | `create` | `useState` / `useReducer` | `reactive` / `shallowRef` | `readable` / `writable` |
| Derived state | `create` | `useMemo` | `computed` | `derived` |
| Lifecycle | `effect` | `useEffect` | `onMounted`, `onUnmounted` | `onMount`, `onDestroy` |
| Dependency injection | `inject` | `useContext` | `inject` | `getContext` |## Redux Devtools
Import `@xoid/devtools` and set a `debugValue` to your atom. It will send values and action names to the Redux Devtools Extension.
```js
import devtools from '@xoid/devtools'
import create from 'xoid'
devtools() // run onceconst $atom = atom(
{ alpha: 5 },
($atom) => {
const $alpha = $atom.focus(s => s.alpha)
return {
inc: () => $alpha.update(s => s + 1),
deeply: { nested: { action: () => $alpha.update((s) => s + 1) } }
}
}
)atom.debugValue = 'myAtom' // enable watching it by the devtools
const { deeply, inc } = atom.actions
inc() // "(myAtom).inc"
deeply.nested.action() // "(myAtom).deeply.nested.action"
atom.focus(s => s.alpha).set(25) // "(myAtom) Update ([timestamp])
```## Finite state machines
No additional syntax is required for state machines. Just use the `create` function.
```js
import { atom } from 'xoid'
import { useAtom } from '@xoid/react'const createMachine = () => {
const red = { color: '#f00', onClick: () => atom.set(green) }
const green = { color: '#0f0', onClick: () => atom.set(red) }
return atom(red)
}// in a React component
const { color, onClick } = useAtom(createMachine)
return
```---
If 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.
## Why **xoid**?
- Easy to learn
- Small bundle size
- Framework-agnostic
- No middlewares needed
- First-class Typescript support
- Easy to work with nested states
- Computed values, transient updates
- Same API to rule them all!
- Global state, Local state, FSMs, Streams
- React, Vue, Svelte, Vanilla JavaScript## Packages
- `xoid` - Core package
- `@xoid/react` - **React** integration
- `@xoid/vue` - **Vue** integration
- `@xoid/svelte` - **Svelte** integration
- `@xoid/devtools` - **Redux Devtools** integration
- `@xoid/reactive` - MobX-like proxy state library over **xoid**## Thanks
This 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.
Following awesome projects inspired **xoid** a lot.
- [Recoil](https://github.com/facebookexperimental/Recoil)
- [zustand](https://github.com/pmndrs/zustand)
- [mobx-state-tree](https://github.com/mobxjs/mobx-state-tree)Thanks to [Anatoly](http://a-maslennikov.com/) for the pencil&ruler icon [#24975](https://www.flaticon.com/free-icon/ruler_245975).
---
If you'd like to support the project, consider sponsoring on OpenCollective: