npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

xoid

v1.0.0-beta.12

Published

Framework-agnostic state management library designed for simplicity and scalability

Downloads

16,088

Readme

xoid is a framework-agnostic state management library. X in its name is an ode to great projects such as ReduX, MobX and Xstate. 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 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.

To install, run the following command:

npm install xoid


Examples

Quick Tutorial

Basic usage of xoid can be learned within a few minutes.

Atom

Atoms are holders of state.

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.

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.

import create 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.

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.

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.

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

import { useAtom } from '@xoid/react'

// in a React component
const state = useAtom(atom)

Vue

<script setup>
import { useAtom } from '@xoid/vue'

const value = useAtom(myAtom)
</script>

<template>
  <div>{{ value }}</div>
</template>

Svelte

<script>
import { useAtom } from '@xoid/svelte'

let atom = useAtom(myAtom)
</script>

<header>{$atom}</header>

🔥 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 state

The following is called a "setup" function:

import { atom, Atom, effect, inject } from 'xoid'
import { ThemeSymbol } from './theme'

export const CounterSetup = ($props: Atom<{ initialValue: number }>) => {
  const { initialValue } = $props.value

  const $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.

import devtools from '@xoid/devtools'
import create from 'xoid'
devtools() // run once

const $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.

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 <div style={{ color }} onClick={onClick} />

If you've read until here, you have enough knowledge to start using xoid. You can refer to the documentation website 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

Following awesome projects inspired xoid a lot.

Thanks to Anatoly for the pencil&ruler icon #24975.


If you'd like to support the project, consider sponsoring on OpenCollective: