Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
October 10, 2019 11:49 pm GMT

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 lets 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.

Lets start with a simple example:

Weve 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>  );}

(follow along on CodeSandbox!)

Now, lets pull those values from an RxJS Observable.

Lets 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);

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>  );}

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. Youll 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 well go with No State and throw in an empty array [] as the second argument.

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

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 lets 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;};

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 weve got this list of names showing now, but this is all very boring so far, so lets do something a little more Asynchronous.

Lets 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)));

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, lets fetch the list of names from an API.

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

Well 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}`;

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, well 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)));

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). Weve 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.


Original Link: https://dev.to/bitovi/rxjs-with-react-jek

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To