Skip to Content
Comparison

Comparison

Two different questions get asked about react-rx: “why this instead of another RxJS binding?” and “why streams instead of my state library?”. They deserve different answers.

RxJS ↔ React bindings

If you already have observables, the bindings differ in update timing, Suspense integration, and how much ceremony each stream needs:

Capabilityreact-rx observable-hooks @react-rxjs/core DIY useEffect
Deferred, identity-coherent updatesbuilt into useObservable— (synchronous)— (synchronous)manual
Suspense data fetchinguse()-compatible promise; streams update in place without re-suspendingObservableResourcesuspends until first valuemanual
<Activity> pre-rendering (React 19.2)fetches start during render; preloadObservablePromise for hover warm-up
Sync emission on first paintno extra re-renderuseObservableEagerStatewith an active subscriptionextra render after mount
Setup per streamnone — pass any Observablenonebind() + <Subscribe> / active subscriptionsubscription plumbing each time
SSRnever throws; server matches the client’s first paintrenders initial statemanual
React Compilertest suite runs through it
React / RxJS supportReact 19.2+, RxJS 7.2+React 16.8+, RxJS 6–7React 16.8+, RxJS 7+any

The honest flip side: observable-hooks and @react-rxjs support much older React versions. react-rx deliberately targets the newest React — the concurrent-rendering rows above are the point. See the Suspense demo and Activity demo for the rows that are easiest to feel.

General state libraries

Zustand, Jotai, XState, and TanStack Query solve different shapes of state, and all of them are good at their shape. The comparison that matters is which shape your state has:

react-rx + RxJSZustand Jotai XState TanStack Query 
Mental modelstreams of values over timeone plain storeatomsstate machines & actorsserver-state cache
Live, multi-emission data (sockets, tokens, presence)first-classmanual set() callsmanual writesobservable/callback actorspolling or manual cache writes
Cancellation & race handlingswitchMap / exhaustMap semanticsmanualAbortSignal in async atomsstop/restart actors on transitioncancels/dedupes per query key
Time (debounce, intervals, “x seconds ago”)operators (timer, debounceTime, auditTime)manualmanualdelayed transitionsstaleTime / refetchInterval
Combining several async sourcescombineLatest / merge / zipmanualderived async atomsmachine orchestrationdependent queries
Request caching & invalidationcompose it yourself (shareReplay, ttl)atom-levelbest-in-class: invalidation, dedupe, persistence
Offline & retryretry / repeat you composebuilt-in retries, refetch-on-reconnect, offline mutation queue
Explicit workflow modelingencoded in stream compositionstatecharts — the whole point
SuspenseuseObservablePromise + use()async atoms suspendmanual (promise actors)useSuspenseQuery
Usable outside ReactRxJS runs anywherevanilla storecreateStoreactors run anywhereframework-agnostic core

When to reach for which

  • react-rx + RxJS when state is live, multi-value, or time-based: streaming tokens, sockets, presence, timers, resumable polling, anything where “the value” is really a sequence of values with cancellation and timing rules.
  • TanStack Query when state is a cache of request/response pairs and you want invalidation, dedupe, and offline mutations without building them.
  • XState when the hard part is which states exist and which transitions are legal — checkout flows, wizards, connection lifecycles.
  • Zustand / Jotai when you need shared client state with minimal ceremony and no streaming semantics.

They compose

These are not either/or choices:

  • XState v5 accepts observables as actor input (fromObservable), so a react-rx codebase can drive machines from the same streams components read.
  • A TanStack Query cache can own request/response state while react-rx owns the live layers on top (subscriptions, streaming updates), or a stream can write into the query cache.
  • Any external store (Zustand included) can be exposed as an observable and read through useObservable alongside the rest of your streams.
Last updated on