RxJS Hooks and Suspense: The Ultimate Guide

In witch I suggested a simple, flexible, testable and performant solution to use RxJS Observable with React hooks and Suspense.

Why Hooks

Stateful logic is unavoidable in any React project. In early days we used the state property in Class-Components to hold stateful values.

“This” isn’t the way

But quickly we realized that it is prone to lose track of states in “this” way. So we divided Components into stateful(smart) Components and stateless(dumb) Components. Stateful logic is delegated to parent stateful Components to keep most Components stateless.

This does not solve the issue, just makes it less painful.

Time travelling

Then came the age of Redux(and MobX etc.). We started to put states into central stores which can be tracked with devtools and stuff.

This does not solve the issue, just delegates it to outside stores.

Introducing stores is acceptable for a full project but would be too bloated for developing reusable stateful Components.

Get on the Hook

React Hooks fills this gap by offering a mechanism that connects side-effects separately within the Component.

For stateful logic it is like connecting to many mini-stores within the Component. Side-effect code with hooks is compact, reusable and testable.

Hooks is an attempt to solve the issue. It is delicate and not perfect but it is the best we have so far.

For more about hooks see the .

Why RxJS in Hooks

Since React hooks opens a door of reusing side-effect logic within Components, it is tempting to reuse complicated asynchronous logic like remote data fetching, intricate animation or device input sequence interpretation.

One of the most popular ways to manage complicated asynchronous logic is , a language-independent declarative programming paradigm concerned with data streams and the propagation of change. RxJS, part of the ReactiveX(Reactive Extensions), is a JavaScript implementation of reactive programming.

There are also libraries that focus only on a few specific asynchronous scenarios, like for remote data fetching. This is like comparing Redux Saga with Redux Observable. The knowledge you gain from learning how to use these libraries is not as transferable as RxJS and Reactive Programming.

Yes there is a learning curve on RxJS but that is mostly a one-time conceptual thing. Don’t be scared by the number of RxJS opertators. You most likely only need a few of them. Also see the .

Observable Hooks

We first tried but quickly encountered some . We also think the useEventCallback is which is a performance issue that is hard to fix due to .

Unfortunately the project is not actively developed as the team has shifted focus to the redux-observable-like project.

Ultimately we rethought the whole integration, redesigned API from the ground up and created for connecting RxJS Observable to React Components.

A simple example(more on the ):

import React from 'react'
import { useObservableState } from 'observable-hooks'
import { timer } from 'rxjs'
import { switchMap, mapTo, startWith } from 'rxjs/operators'
const App = () => {
const [isTyping, updateIsTyping] = useObservableState(
event$ => event$.pipe(
switchMap(() =>
timer(1000).pipe(
mapTo(false),
startWith(true)
)
)
),
false
)
return (
<div>
<input type="text" onKeyDown={updateIsTyping} />
<p>{isTyping ? 'Good you are typing.' : 'Why stop typing?'}</p>
</div>
)
}

By decoupling states, events and Observables it no longer makes unused resources run idle.

Logic lives in pure function which improves reusability and testability.

See the docs for more about and .

Example

Suspense

With the experimental asynchronous resources can be read declaratively like it has already been resolved.

Since Suspense is just a mechanism it is possible to convert Observables into Suspense compatible resources ().

Observable-hooks offers to do the trick.

// api.js
import { ObservableResource } from 'observable-hooks'
const postResource$$ = new Subject()export const postsResource = new ObservableResource(postResource$$.pipe(
switchMap(id => fakePostsXHR(id))
))
export function fetchPosts(id) {
postResource$$.next(id)
}

Resources are consumed with .

// App.jsx
import { useObservableSuspense } from 'observable-hooks'
import { postsResource, fetchPosts } from './api'fetchPosts('crimx')function ProfilePage() {
return (
<Suspense fallback={<h1>Loading posts...</h1>}>
<ProfileTimeline />
</Suspense>
)
}
function ProfileTimeline() {
// Try to read posts, although they might not have loaded yet
const posts = useObservableSuspense(postsResource)
return (
<ul>
{posts.map(post => (
<li key={post.id}>{post.text}</li>
))}
</ul>
)
}

Conclusion

The API of observable-hooks is really simple and flexible. Folks who love both React and RxJS I highly recommend you give it a try.

What do you think? Please let us know by leaving a comment below!

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store