Reatom

Tiny and powerful reactive system with immutable nature

README

Reatom is a state manager with quite unique set of features, it provides the most modern techniques for describing, executing, and debugging code in a tiny package. It opinionated data manager with strict, but flexible rules, which allows you to write simple and maintainable code.

Key principles are immutabilityand explicit reactivity(no proxies), implicit DIand actor-like lifecycle hooks. All this with simple API and automatic type inference.

The core package is included all this features and you may use it anywhere, from huge apps to even small libs, as the overhead only 2 KB . Also, you could reuse our carefully written helper tools to solve complex tasks in a couple lines of code. We trying to build stable and balanced ecosystem for perfect DX and predictable maintains even for years ahead.

Do you React.js user? Check out npm-react package!

Simple example


vanilla repl

react repl

  1. ``` ts
  2. import { action, atom, createCtx } from '@reatom/core'

  3. // primitive mutable atom
  4. const inputAtom = atom('')
  5. // computed readonly atom
  6. // `spy` reads the atom and subscribes to it
  7. const greetingAtom = atom((ctx) => `Hello, ${ctx.spy(inputAtom)}!`)

  8. // all updates in action processed by a smart batching
  9. const onInput = action((ctx, event) => {
  10.   // update the atom value by call it as a function
  11.   inputAtom(ctx, event.currentTarget.value)
  12. })

  13. // global application context
  14. const ctx = createCtx()

  15. document
  16.   .getElementById('name-input')
  17.   .addEventListener('input', (event) => onInput(ctx, event))

  18. ctx.subscribe(greetingAtom, (greeting) => {
  19.   document.getElementById('greeting').innerText = greeting
  20. })
  21. ```

Check out @reatom/core docs for detailed explanation of key principles and features.

Advanced example


repl

We will use @reatom/core , @reatom/npm-react , @reatom/async and @reatom/hooks packages in this example by importing it from the meta package @reatom/framework .

reatomAsyncis a simple decorator which wrap your async function and adds extra actions and atoms to track creating promise statuses.

withDataAtomadds property dataAtomwhich subscribes to the effect results, it is like a simple cache implementation. withAbortallow to define concurrent requests abort strategy, by using ctx.controller(AbortController ) fromreatomAsync. withRetryand onRejecthandler helps to handle temporal rate limit.

Simple sleephelper (for debounce) gotten from utils package - it is a built-in microscopic lodash alternative for most popular and tiny helpers.

onUpdateis a hook which subscribes to the atom and call passed callback on every update.

  1. ``` ts
  2. import {
  3.   atom,
  4.   reatomAsync,
  5.   withAbort,
  6.   withDataAtom,
  7.   withRetry,
  8.   sleep,
  9.   onUpdate,
  10. } from '@reatom/framework'
  11. import { useAtom } from '@reatom/npm-react'
  12. import * as api from './api'

  13. const searchAtom = atom('', 'searchAtom')
  14. const fetchIssues = reatomAsync(async (ctx, query: string) => {
  15.   await sleep(250)
  16.   const { items } = await api.fetchIssues(query, ctx.controller)
  17.   return items
  18. }, 'fetchIssues').pipe(
  19.   withDataAtom([]),
  20.   withAbort({ strategy: 'last-in-win' }),
  21.   withRetry({
  22.     onReject(ctx, error: any, retries) {
  23.       return error?.message.includes('rate limit')
  24.         ? 100 * Math.min(500, retries ** 2)
  25.         : -1
  26.     },
  27.   }),
  28. )
  29. onUpdate(searchAtom, fetchIssues)

  30. export default function App() {
  31.   const [search, setSearch] = useAtom(searchAtom)
  32.   const [issues] = useAtom(fetchIssues.dataAtom)
  33.   const [isLoading] = useAtom(
  34.     (ctx) =>
  35.       ctx.spy(fetchIssues.pendingAtom) + ctx.spy(fetchIssues.retriesAtom) > 0,
  36.   )

  37.   return (
  38.     <main>
  39.       <input
  40.         value={search}
  41.         onChange={(e) => setSearch(e.currentTarget.value)}
  42.         placeholder="Search"
  43.       />
  44.       {isLoading && 'Loading...'}
  45.       <ul>
  46.         {issues.map(({ title }, i) => (
  47.           <li key={i}>{title}</li>
  48.         ))}
  49.       </ul>
  50.     </main>
  51.   )
  52. }
  53. ```

The whole logic definition is only about 15 LoC, what is the count would be in a different library? The most impressed thing is that the overhead is only 6KB (gzip) and you not limited only for network cache, Reatom is enough powerful and expression for describing any kind of states.

To get maximum of Reatom and the ecosystem just go to tutorial . If you need something tiny - check out the core package docs . Also, we have a package for testing !

Roadmap


Finish forms package
Finish persist and navigation packages
Add adapters for most popular ui frameworks: react , angular, vue, svelte, solid.
Port some components logic from reakit.io, to made it fast, light and portable.
Add ability to made async transaction and elaborate optimistic-ui patterns and helpers / package.
Try to write own jsx renderer.

FAQ


Why not X?


Reduxis awesome and Reatom is heavy inspired by it. Immutability, separation of computations and effects are good architecture designs principles. But there are a lot of missing features, when you trying to build something huge, or want to describe something small. Some of them is just impossible to fix, like batching , O(n) complexity or that selectors is not inspectable and breaks the atomicy . Others is really hard to improve . And boilerplate, yeah, the difference is a huge . Reatom solves all this problems and bring much more features by the almost same size.

MobXbrings too big bundle to use it in a small widgets, Reatom is more universal in this case. Also, MobX have mutability and implicit reactivity, which is usefull for simple cases, but could be not obvious and hard to debug in complex cases. There is no separate thing like action / event / effect to describe some dependent effects sequences (FRP-way). There is not atomicy too .

Effectoris too opinionated. There is nofirst-class support for lazyreactive computations and all connections are hot everytime, which is could be more predictable, but defenetly is not optimal. Effector is not friendly for fabric creation (because of it hotness), which disallow us to use atomization patterns, needed to handle immutability efficient. The bundle size is 2-3 times larger and performance is lower .

Zustand , nanostores , xstate and many other state managers have no so great combination of type inference, features, bundle size and performance, as Reatom have.

Why immutability?


Immutable data is much predictable and better for debug, than mutable states and wrapers around that. Reatom specialy designed with focus on simple debug of async chains and have a patterns to handle greate performance .

What LTS policy is used and what about bus factor?


Reatom always developed for long time usage. Our first LTS (Long Time Support) version (v1) was released in December 2019 and in 2022 we provided breaking changes less Migration guid to the new LTS (v3) version. 3 years of successful maintains is not ended, but continued in adapter package . We hope it shows and prove our responsibility.

To be honest, right now bus factor is one, @artalar - the creator and product owner of this, but it wasn't always like this as you can see . Reatom PR wasn't great in a past couple of years and a lot of APIs was experimental during development, but now with the new LST version (v3) we bring to new feature of this lib and application development experience for a long time.

How performant Reatom is?


Here is the benchmark of complex computations for different state managers. Note that Reatom by default uses immutable data structures, works in a separate context (DI-like) and keeps atomicity , which means the Reatom test checks more features, than other state manager tests. Anyway, for the middle numbers Reatom faster than MobX which is pretty impressive.

Also, check out atomization guild .

Community


en
ru
twitter

How to support the project?


https://www.patreon.com/artalar_dev