Reatom: State Management That Grows With You
Source: Dev.to
The Fragmentation Problem
Modern frontend development has a familiar pattern:
- Start with simple
useStatehooks - Need shared state? Add Context
- Context re‑renders too much? Add a state manager
- State manager doesn’t handle async well? Add a data‑fetching library
- Need forms? Another library
- Routes need data loading? Router with loaders
- Persistence? Yet another library
- Logging and analytics? Here we go again…
Each tool has its own mental model, API quirks, and bundle cost. The stack grows, complexity multiplies, and new team members need weeks to understand it all. Versioning and maintaining compatibility across multiple libraries becomes a nightmare.
Reatom is a different approach. One coherent system that can handle all of these concerns — from the simplest counter to the most complex enterprise data flows. Use what you need, integrate with what you have.
For a fast overview, check out the site:
The Zen
Four principles guide Reatom’s design:
- Primitives outperform frameworks – A few powerful building blocks beat a complex framework.
- Composition beats configuration – Stack simple pieces instead of configuring monoliths.
- Explicit specifics, implicit generics – Be clear about what matters, hide the boilerplate.
- Compatibility worth complexity – Works with your stack, not against it.
These aren’t marketing slogans. They’re the result of six years of production use and continuous refinement, starting with the first LTS release in December 2019.
Start Simple
If signals are familiar, Reatom will feel natural:
import { atom, computed } from '@reatom/core'
const counter = atom(0)
const isEven = computed(() => counter() % 2 === 0)
// Read
console.log(counter()) // 0
console.log(isEven()) // true
// Write
counter.set(5)
counter.set(prev => prev + 1)
No providers, no boilerplate, no ceremony. The core is less than 3 KB gzipped — smaller than some “lightweight” alternatives.
You can stop there, but here’s where it gets interesting…
Grow Infinitely
The same primitives that power a counter can handle enterprise‑grade complexity:
import {
atom,
computed,
withAsyncData,
withSearchParams,
} from '@reatom/core'
// Sync with URL search params automatically
const search = atom('', 'search').extend(withSearchParams('search'))
const page = atom(1, 'page').extend(withSearchParams('page'))
// Async computed with automatic cancellation
const searchResults = computed(async () => {
await wrap(sleep(300)) // debounce
const response = await wrap(fetch(`/api/search?q=${search()}&page=${page()}`))
return await wrap(response.json())
}, 'searchResults').extend(withAsyncData({ initState: [] }))
// Usage
searchResults.ready() // loading state
searchResults.data() // the results
searchResults.error() // any errors
What happens here
searchandpageatoms automatically sync with URL parameters.- The computed re‑runs only when dependencies change and when something subscribes to it (lazy).
- If the user types faster than the API responds, previous requests are automatically cancelled, eliminating race conditions and memory leaks.
This is the same atom from the counter example — just extended.
The Extension System
Instead of a monolithic framework, Reatom provides composable extensions that stack cleanly:
const theme = atom('dark').extend(
// Persist to localStorage
withLocalStorage('theme')
)
const list = atom([]).extend(
// Memoize equal changes
withMemo()
)
const unreadCount = atom(0).extend(
// React to state changes
withChangeHook(count => {
document.title = count > 0 ? `(${count}) My App` : 'My App'
}),
// Sync across tabs
withBroadcastChannel('notificationsCounter')
)
Each extension does one thing well. Compose exactly what’s needed.
Beyond State: Complete Solutions
Forms
Type‑safe form management with first‑class support for:
- Field‑level and form‑level validation (sync and async)
- Integration with standard schema validators (Zod, Valibot, etc.)
- Dynamic field arrays with add, remove, reorder operations
- Focus tracking, dirty detection, error management
- No weird string paths — just objects and atoms
- Extremely optimized and performant
Routing
Type‑safe routing with automatic lifecycle management:
- Parameter validation and transformation
- Data loading with automatic cancellation on navigation
- Nested routes with shared layouts
- Search‑parameter handling
- Isomorphic cross‑framework support
Factory pattern – state created in route loaders is automatically garbage‑collected on navigation, solving the “global state cleanup” problem.
Persistence
A ton of built‑in storage adapters with advanced features:
localStorage,sessionStorage,BroadcastChannel, IndexedDB, Cookie and Cookie Store- Integration with standard schema validators (Zod, Valibot, etc.)
- Version migrations for data‑format changes
- TTL (time‑to‑live) support
- Graceful fallback to memory storage when unavailable
Deep Technical Advantages
Cause Tracking
Reatom emulates TC39 AsyncContext and gains many benefits:
- Automatic cancellation of concurrent asynchronous chains (kind of debounce)
- Some IoC/DI possibilities
- Async transactions
- Process tracking and logging
The last feature is a game‑changer for complex async flows. You can easily inspect the cause of concurrent operations and reduce debugging time from hours to minutes.
Performance
Reatom delivers top‑tier performance given its feature set. The more complex the application, the faster Reatom performs compared to alternatives. Benchmarks for complex computations show Reatom outperforming MobX in moderately sized dependency graphs, impressive considering Reatom uses immutable data structures and operates in a separate async context—features that usually add overhead.
Explicit Reactivity, No Proxies
Proxy‑based reactivity can be convenient for simple cases but becomes hard to trace in large codebases. With Reatom, hover over any property for a type hint — it’s always clear what’s reactive.
Reatom encourages atomization — transforming backend DTOs into application models where mutable properties become atoms:
// Backend DTO
type UserDto = { id: string; name: string }
// Application model with explicit reactivity
type User = { id: string; name: Atom }
const userModel = { id: dto.id, name: atom(dto.name) }
// Usage
userModel.id // static value — no reactivity
userModel.name() // reactive — tracks changes
This pattern gives fine‑grained control: define reactive elements precisely where needed, avoid uncontrolled observer creation, and keep the reactive graph explicit.