DEV Community

Cover image for RxJS with React
Adam L Barrett for Bitovi

Posted on • Originally published at bitovi.com

RxJS with React

RxJS and React go together like chocolate and peanut butter: great individually but they become something incredible when put together.

2 people colliding and accidentally mixing their chocolate bar and peanut butter, but then seemingly enjoying the mix

A quick search on npm will find a slew of hooks to connect RxJS Observables to React components, but let’s start at the beginning, because RxJS and React fit very well together "as is" because they follow the same philosophy and have very compatible APIs.

A quick aside about Why RxJS

2019 has been the year of RxJS, blowing up across the web-dev community with events like rxjs.live and ng-conf. More and more developers are finding out that RxJS is awesome and it is totally worth climbing your way through the somewhat steep learning curve.

Angular devs have been using RxJS for a while now. A quick search will find vue-rx, ember-rx, and even Svelte can use RxJS Observables as stores by default. When you learn RxJS you are learning a highly portable skill that can be used across frameworks. The concepts of Rx can actually be used across languages and platforms.

RxJS is a mature, battle hardened library for dealing with events and data flow. It is definitely going to be valuable to familiarize yourself with how it works.


Let’s start with a simple example:

We’ve got a simple List component here that just lists the strings it is given:

const source = ['Adam', 'Brian', 'Christine'];

function App() {
  const [names, setNames] = useState(source);

  return (
    <div className="App">
      <h1>RxJS with React</h1>

      <List items={names} />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

(follow along on CodeSandbox!)

Now, let’s pull those values from an RxJS Observable.

Let’s start by creating an Observable with the RxJS of() function.

We'll need to:

  • add rxjs as a dependency (npm i rxjs, yarn add rxjs or however you need to if you're not using CodeSandbox)
  • import of from rxjs

Then let's create an Observable called names$, whose value is the source array:

import { of } from 'rxjs';

const source = ['Adam', 'Brian', 'Christine'];
const names$ = of(source);
Enter fullscreen mode Exit fullscreen mode

FYI: I will be following the convention of naming an Observable variable with a \$ suffix (aka Finnish Notation), which is completely optional but I think it may help for clarity while learning.

Now what we want to do is synchronize the component state with the state from the Observable. This would be considered a side-effect of the React function component App, so we are going to use the useEffect() hook, which we can import from react.

Inside the useEffect() callback we will:

  • subscribe to the names$ Observable with the subscribe() method, passing our "state setter function" setNames as the observer argument
  • capture the subscription returned from observable.subscribe()
  • return a clean-up function that calls the subscriptions .unsubscribe() method
function App() {
  const [names, setNames] = useState();

  useEffect(() => {
    const subscription = names$.subscribe(setNames);
    return () => subscription.unsubscribe();
  });

  return (
    <div className="App">
      <h1>RxJS with React</h1>
      <List items={names} />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Which at this point should look something like this:

The concepts and APIs in RxJS and React are very compatible: the way useEffect aligns with an RxJS subscription and how the clean-up call is a perfect time to unsubscribe. You’ll see a lot more of that "symbiosis" as we go on.

An aside about useEffect

When using useEffect to synchronize component state to some "outer" state, you must decide what state you want to sync with.

  • All state
  • No state
  • Some select pieces of state

This is represented in the deps array, which is the second argument passed to useEffect.

To use an quote from Ryan Florence:

useEffect(fn) // all state
useEffect(fn, []) // no state
useEffect(fn, [these, states])

So, in this instance we don't have any props or other state to sync with: we just want our names array to be whatever is the current value of our Observable. We just want to update our component state whenever the Observables value changes, so we’ll go with No State and throw in an empty array [] as the second argument.

useEffect(() => {
  const subscription = names$.subscribe(setNames);
  return () => subscription.unsubscribe();
}, []);
Enter fullscreen mode Exit fullscreen mode

Creating a custom hook

It looks like we'll be using this pattern a lot:

  • subscribing to an Observable in useEffect
  • setting the state on any changes
  • unsubscribing in the clean-up function

...so let’s extract that behaviour into a custom hook called useObservable.

const useObservable = observable => {
  const [state, setState] = useState();

  useEffect(() => {
    const sub = observable.subscribe(setState);
    return () => sub.unsubscribe();
  }, [observable]);

  return state;
};
Enter fullscreen mode Exit fullscreen mode

Our useObservable hook takes an Observable and returns the last emitted value of that Observable, while causing a re-render on changes by calling setState.

Note that our state is initialized as undefined until some value is emitted in the Observable. We'll use that later, but for now, make sure the components can handle when the state is undefined.

So we should have something like this now:

Of course, we could, and probably should, have useObservable() defined as an export from a module in its own file because it is shareable across components and maybe even across apps. But for our simple example today, we'll just keep everything in one file.

Adding some asynchronicity

So we’ve got this list of names showing now, but this is all very boring so far, so let’s do something a little more Asynchronous.

Let’s import interval from rxjs and the map operator from rxjs/operators. Then, let's use them to create on Observable that only adds a name to the list every second.

import { interval } from 'rxjs';
import { map } from 'rxjs/operators';

const source = ['Adam', 'Brian', 'Christine'];
const names$ = interval(1000).pipe(map(i => source.slice(0, i + 1)));
Enter fullscreen mode Exit fullscreen mode

Neat. So we can see our list appearing one at a time. Sort of useless, but off to a good start. 😄

Fetching some data

Instead of our source array, let’s fetch the list of names from an API.

The API endpoint we’ll be using comes from randomuser.me, which is a nice service for just getting some made up user data.

We’ll add these 2 helper variables, api and getName which will allow us to fetch 5 users at a time and the function will help extract the name from the user data randomuser.me provides.

const api = `https://randomuser.me/api/?results=5&seed=rx-react&nat=us&inc=name&noinfo`;
const getName = user => `${user.name.first} ${user.name.last}`;
Enter fullscreen mode Exit fullscreen mode

RxJS has some great utility functions for fetching data such as fromFetch and webSocket, but since we are just getting some JSON from an ajax request, we’ll be using the RxJS ajax.getJSON method from the rxjs/ajax module.

import { ajax } from 'rxjs/ajax';

const names$ = ajax
  .getJSON(api)
  .pipe(map(({ results: users }) => users.map(getName)));
Enter fullscreen mode Exit fullscreen mode

This will fetch the first 5 users from the API and map over the array to extract the name from the name.first and name.last property on each user. Now our component is rendering the 5 names from the API, yay!

It's interesting to note here, that since we moved our code into a custom hook, we haven't changed the component code at all. When you decouple the data from the display of the component like this, you get certain advantages. For example, we could hook up our Observable to a websocket for live data updates, or even do polling in a web-worker, but the component doesn't need to change, it is happy rendering whatever data it is given and the implementation of how the data is retrieved is isolated from the display on the page.

Aside about RxJS Ajax

One of the great benefits of using the RxJS ajax module (as well as fromFetch), is that request cancellation is built right in.

Because our useObservable hook unsubscribes from the Observable in the clean-up function, if our component was ever “unmounted” while an ajax request was in flight, the ajax request would be cancelled and the setState would never be called. It is a great memory safe feature built in without needing any extra effort. RxJS and React working great together, out of the box, again.

Reese's Peanut Butter Cups


Actions

So now we have this great custom hook for reading state values off an Observable. Those values can come from anywhere, asynchronously, into our component, and that is pretty good, but React is all about Data Down and Actions Up (DDAU). We’ve really only got the data half of that covered right now, what about the actions?

In the next instalment of the series we'll explore Actions, how we model our RxJS integration after the built-in useReducer hook, and much much more.

If you have any questions, feel free to post in the comments, or you can join our Bitovi community Slack at https://bitovi.com/community/slack, and ask me directly. There are lots of other JavaScript experts there too, and it is a great place to ask questions or get some help.

Top comments (9)

Collapse
 
briunsilo profile image
Brian Pedersen

Great tutorial! The only thing I missed was TypeScript annotations. Here they are for any one else needing them.

// Creating a custom hook
function useObservable<T>(observable: Observable<T>) {
    const [state, setState] = useState<T>();

    useEffect(() => {
        const sub = observable.subscribe(setState);
        return () => sub.unsubscribe();
    }, [observable]);

    return state;
}
// ...
const names = useObservable<string[]>(names$);


// Fetching some data
const getName = (user: User) => `${user.name.first} ${user.name.last}`;

const names$ = ajax.getJSON<{ results: User[] }>(api).pipe(
    map(({ results: users }) => users.map(getName))
);
Enter fullscreen mode Exit fullscreen mode
Collapse
 
mateiadrielrafael profile image
Matei Adriel • Edited

Ive used the rxjs-hooks for my logic gate simulator and it worked great! I've also used it with sapper for visual studio paint, the lack of proper ts support in sapper wasn't that good but except for that it was a really nice experience Rxjs is definitly my favorite lib to use for side projects:)

Collapse
 
mohdule profile image
Mohannad Salah • Edited

Thanks mate, this has been very very helpful.
The first time i ever encountered RxJS is when i started learning Angular, and i was really scared for no reason (maybe because of the Angular learning curve at the time i started learning it), then i stopped learning Angular and RxJS.

This is post here marks my comeback, thanks a lot

Collapse
 
qadsoch profile image
HaQadoschJS

Thanks for that article!
Really useful and you make RxJs easy to start with by building on your examples.

Keep them coming!

Collapse
 
quickfingersdev profile image
quickfingers

This is really great! Do you prefer RxJS over redux?

Collapse
 
bigab profile image
Adam L Barrett

I do. Though I have used both together (redux-observable), the main issues I have with redux are:

  • If I've subscribed to the store (or slice of the store) why do I also have to dispatch an action that says "go get this data" as well
  • I often just want to respond to a state change, like "when this id changes, let me go fetch the details of that thing and display them" as opposed to needing to know what action changed the id and respond to that with more actions for the fetching the details

Hope that makes sense, I should probably write a blog post about it

Collapse
 
quickfingersdev profile image
quickfingers • Edited

Hi Adam, thank you for this very informative reply. I have been watching your youtube presentation. Do you any github example source on a real app? I just want to see it in action on a bigger and complex app.

Thread Thread
 
aksh0314 profile image
aksh0314

Yeah, that would be very helpful Adam if you have any example for complex app. Thanks

Collapse
 
aksh0314 profile image
aksh0314

Wow, that was a great article and very informative. Made it understandable very easily. Thanks a lot for this.