xoid is a framework-agnostic state management library, about 1kB gzipped, designed around one idea: an API, with a low surface area, that can represent local state, global state, derived state, finite state machines, and even observable streams without switching mental models.
In React especially, we routinely switch between different ways of thinking about state:
- useState or useReducer for local state
- a global store for shared state
- selectors or memoization for derived state
- external libraries for streams or reactions
Each switch forces a context change. The APIs look different even when the underlying problem is the same.
Many companies still use Redux for global state, then there's reselect for derived state, maybe there's also RxJS used in sync with some parts of the Redux state. The problem is not necessarily any of these tools, but fragmentation across tools.
xoid is an attempt to collapse most of that surface area into a single primitive.
Atoms as the only primitive
An atom is a holder of state.
import { atom } from 'xoid'
const $count = atom(3)
$count.set(5)
$count.update(s => s + 1)
Atoms expose their current value, allow immutable updates, and support subscriptions explicitly. There is no magic and no hidden dependency tracking.
Atoms can also define actions colocated with the state they operate on, which keeps mutation logic close to the data.
const $count = atom(5, (a) => ({
increment: () => a.update(s => s + 1),
decrement: () => a.value--
}))
This already covers a large portion of what people use reducers for, without introducing a second abstraction.
Derived state, inspired by Recoil
Derived state is built into the core API.
xoid’s derived atoms were heavily inspired by Recoil. A derived atom declares how its value is computed from other atoms.
const $a = atom(3)
const $b = atom(5)
const $sum = atom(read => read($a) + read($b))
Derived atoms are lazily evaluated. Their computation only runs when something actually subscribes. This keeps the runtime cost predictable.
For the common case of deriving from a single atom, there is also a shortcut.
const $double = $a.map(s => s * 2)
This .map method is not just syntactic sugar. It is one of the defining characteristics of xoid.
Focus and map as first-class operations
xoid includes two methods that are intentionally first-class: .focus and .map.
.focus acts like a lens into nested state.
const $state = atom({ deeply: { nested: { alpha: 5 } } })
const $alpha = $state.focus(s => s.deeply.nested.alpha)
$alpha.set(6)
Because updates are immutable, changing a focused branch replaces the root atom with a new value while preserving structural sharing.
This makes nested state ergonomic without requiring reducers, immer, or structural cloning utilities.
.map, on the other hand, treats atoms as streams of values. Mapping an atom creates a derived atom that updates whenever the source updates.
Together, focus and map make it possible to work comfortably with deeply nested state and reactive transformations without pulling in an additional observable or stream library.
This is a key difference from many atomic state libraries. There are excellent alternatives in this space, such as nanostores, but xoid deliberately leans into focus and map as core operations rather than extensions. The result is an API that handles nested state and reactive derivation in a single place.
Subscriptions are explicit
Atoms expose two subscription methods: subscribe and watch.
const unsub = $atom.subscribe((state, prev) => {
console.log(state, prev)
})
Nothing is implicit. There is no hidden dependency graph. Subscriptions are straightforward callbacks with clear lifetimes.
This makes atoms usable outside of UI frameworks just as easily as inside them.
Framework integrations without ceremony
xoid integrates with React, Vue, and Svelte using a single hook: useAtom.
There are no providers, no configuration steps, and no framework-specific concepts leaking into the core.
import { useAtom } from '@xoid/react'
const value = useAtom(myAtom)
The same atom can be consumed from React, Vue, Svelte, or plain JavaScript without modification.
This symmetry is intentional. The core library has no notion of components or renders.
If that sounds appealing, the documentation and examples are all here.
Curious feedback is very welcome.
