Fetching Data in React with RxJS and &lt;$&gt; fragment
Tags: react, rxjs, javascript, webdev
Published: 2020-08-04 08:49:33

We often need to [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) data in our components. Here's an example using useState hook and [fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) to get and display some data:

```jsx
function App(){
  const [data, setData] = useState(null);

  // useEffect for fetching data on mount
  useEffect(() => {
    fetch('//...')
    .then(response => response.json())
    .then(data => setData(data));
  }, []);

  return <div>Data: { data }</div>
}
```

<figcaption>here and further I'll drop checking <code>response.ok</code> for brevity sake</figcaption>

Looks alright?

Well, this approach lacks few important features:

- cancelling fetching on component unmount (e.g if user leaves current page)
- handling errors
- displaying loading indicator

To handle all these issues nicely we'll use RxJS!

[RxJS](https://rxjs.dev/) is a very mighty tool to manage and coordinate async events (like fetching and UI events). Learning it will pay you back 10 fold!

_Please, don't get freaked out now, I'll walk you through adding and using it_ 🙂

**tl;dr:** [resulting app playground](https://stackblitz.com/edit/react-rxjs-elements-devto-react-fetching?file=index.tsx) and [<$> fragment library](https://github.com/kosich/react-rxjs-elements#readme)

---

Let's start with updating our App to use RxJS!

## 🔋 Power Up

First we'll switch to RxJS' [fromFetch](https://ncjamieson.com/understanding-fromfetch/) — it's a wrapper around [native fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API):

```jsx
function App(){
  const [data, setData] = useState(null);

  useEffect(() => {
    fromFetch('//...')
      .subscribe(response =>
        response.json().then(data => setData(data))
      );
  }, []);

  return <div>Data: { data }</div>
}
```

`.subscribe` method is an analogue for `.then` in Promises — it will receive value updates from the RxJS stream (currently it will handle only one update, but there'll be more)

Also `.subscribe` returns an object with which we can cancel the "subscription". This will help us solve our first issue: cancelling fetching on component unmount.

```jsx
function App(){
  const [data, setData] = useState(null);

  useEffect(() => {
    const subscription = fromFetch('//...')
      .subscribe(response =>
        response.json().then(data => setData(data))
      );

    // this function will be called on component unmount
    // it will terminate the fetching
    return () => subscription.unsubscribe();
  }, []);

  return <div>Data: { data }</div>
}
```

_See React's [useEffect#cleaning-up-an-effect](https://reactjs.org/docs/hooks-reference.html#cleaning-up-an-effect) docs section for details_

Hurray: 1 done, 2 left!

---

Let's do a small cleanup before we go further:

## 🔧 Refactoring and <$> fragment

As you can see, we're using `response.json()` async operation inside our `subscribe` function — this is a bad practice for a number of reasons: this stream would not be reusable and cancellation wont work if we're already on stage of `response.json()` parsing.

We'll use a `mergeMap` RxJS operator to fix this:

```jsx
function App(){
  const [data, setData] = useState(null);

  useEffect(() => {
    const subscription = fromFetch('//...')
      .pipe(
        // mergeMap is an operator to do another async task
        mergeMap(response => response.json())
      )
      .subscribe(data => setData(data));

    return () => subscription.unsubscribe();
  }, []);

  return <div>Data: { data }</div>
}
```

_**UPD**: @benlesh made a good point that one can use RxJS' [ajax.getJSON](https://rxjs.dev/api/ajax/ajax) instead of fetch wrapper, and skip the `mergeMap`. E.g.: `ajax.getJSON(url).subscribe(/* etc. */)`. I will keep the `fromFetch` approach for educational and laziness reasons_ 🙂

We've separated `response.json()` operation from results handling. And with our `subscribe` handler only responsible for displaying data — we can now use `<$>` fragment!

[**<$>**](https://github.com/kosich/react-rxjs-elements#readme) — is a small (1Kb) package to display RxJS values in our React components.

It will subscribe to provided stream for us and display updates in place. And also unsubscribe on component unmount, so we won't need to worry about that too!

```jsx
function App(){
  // we need useMemo to ensure stream$ persist
  // between App re-renders
  const stream$ = useMemo(() =>
    fromFetch('//...')
      .pipe(
        mergeMap(response => response.json())
      )
  , []);

  return <div>Data: <$>{ stream$ }</$></div>
}
```

_Note that we've dropped `useState` and `.subscribe`: [<$>](https://github.com/kosich/react-rxjs-elements#readme) does all that!_

---

So, we're ready to add more operators to continue solving our tasks. Let's add a loading indicator!

## ⏳ Loading indicator

```jsx
function App(){
  const stream$ = useMemo(() =>
    fromFetch('//...')
      .pipe(
        mergeMap(response => response.json()),
        // immediately show a loading text
        startWith('loading...')
      )
  , []);

  return <div>Data: <$>{ stream$ }</$></div>
}
```

`startWith` will prepend async data stream with provided value. In our case it looks somewhat like this:

```
start -o---------------------------o- end
                                    
       ^ show 'loading'            ^ receive and display
       | immediately               | response later
``` 

Awesome: 2 done, 1 left!

---

We'll handle errors next:

## ⚠️ Error handling

Another operator [`catchError`](https://thinkrx.io/rxjs/catch/) will let us handle error from fetching:

```jsx
function App(){
  const stream$ = useMemo(() =>
    fromFetch('//...')
      .pipe(
        mergeMap(response => response.json()),
        catchError(() => of('ERROR')),
        startWith('loading...')
      )
  , []);

  return <div>Data: <$>{ stream$ }</$></div>
}
```

Now if fetching fails — we'll display 'ERROR' text.

_If you want to dig deeper, I wrote a detailed article on managing errors: ["Error handling in RxJS or how not to fail with Observables"](https://medium.com/@kddsky/error-handling-in-rxjs-bac0f96a7def) — suppressing, strategic fallbacks, retries simple and with exponential delays — it's all there._

3 done, 0 left!

---

Let's finalize with moving some `div`s around:

## 🖼 Better UI

Most likely we'd like to show properly highlighted error and styled (maybe even animated) loading indicator. To do that — we'll simply move our JSX right into the stream:

```jsx
function App(){
  const stream$ = useMemo(() =>
    fromFetch('//...')
      .pipe(
        mergeMap(response => response.json()),
        // now we'll map not only to text
        // but to JSX
        map(data => <div className="data">Data: { data }</div>),
        catchError(() => of(<div className="err">ERROR</div>)),
        startWith(<div className="loading">loading...</div>)
      )
  , []);

  return <$>{ stream$ }</$>
}
```

Note that now we can fully customize view for each state!

## 🍰 Bonus: anti-flickering

Sometimes if the response comes too quickly we'll see the loading indicator flash for a split second. This is generally undesirable since we've worked long on our loading indicator animation and want to ensure user sees it through 🙂

To fix this we'll split out fetching Observable creation and join the fetching with a 500ms delay:

```jsx
function App(){
  const stream$ = useMemo(() =>
    customFetch('//...').pipe(
        map(data => <div className="data">Data: { data }</div>),
        catchError(() => of(<div className="err">ERROR</div>)),
        startWith(<div className="loading">loading...</div>)
      )
  , []);

  return <$>{ stream$ }</$>
}

function customFetch(URL) {
  // wait for both fetch and a 500ms timer to finish
  return zip(
    fromFetch(URL).pipe( mergeMap(r => r.json()) ),
    timer(500) // set a timer for 500ms
  ).pipe(
    // then take only the first value (fetch result)
    map(([data]) => data)
  )
}
```

Now our loved user will see the loading animation for at least 500ms!

4 done, 🍰 left!

---

A few final words:

## 🎉 Outro

Here's our [resulting app](https://stackblitz.com/edit/react-rxjs-elements-devto-react-fetching?file=index.tsx) if you want to play around with it.

To start using RxJS in your React components just do:

```
npm i rxjs react-rxjs-elements
```

And then drop a stream inside `<$>`:

```jsx
import { timer } from 'rxjs';
import { $ } from 'react-rxjs-elements';

function App() {
  return <$>{ timer(0, 1000) } ms</$>
}
```

That's it, I hope you've learnt something new!

**Thank you for reading this article! Stay reactive and have a nice day** 🙂

If you enjoyed reading — please, indicate that with ❤️ 🦄 📘 buttons

[Follow me on twitter](https://twitter.com/kddsky) for more React, RxJS, and JS posts:

{% twitter 1285580447175643138 %}

**The End**

_Thanks to @niklas_wortmann and @sharlatta for reviewing!_