A practical example of Suspense in React 18
Tags: react, javascript, webdev
Published: 2022-06-13 21:56:26

The recent release of React 18 brought a lot of changes, nothing that will break the code you already written, but a lot of improvements, and some new concepts.
It also made realize a lot of devs, including me, that we used the `useEffect` hook the wrong way.
But in our defence we got tricked by the name, as `useEffect` shouldn't really be used for effects (as [this video explains](https://www.youtube.com/watch?v=HPoC-k7Rxwo)).
In React 18, while you can still use `useEffect` to do things like populating your state with data you read from an API endpoint, they made it clear that we shouldn't really use it for that purpose, and in fact if you enable [StrictMode](https://reactjs.org/docs/strict-mode.html) in your application, in development mode you will find out that using `useEffect` to will be invoked twice, because now React will mount your component, dismount, and then mount it again, to check if your code is working properly.

## Here comes `Suspense`

What we should use instead is the new component `Suspense` (well, it was already present in React 17, but now it's the recommended way), and the component will work like this:

```jsx
<Suspense fallback={<p>Loading...</p>}>
  <MyComponent />
</Suspense>
```

The code above wraps a component, which is loading the data from some datasource, and it will show a fallback until the data fetching is complete.

## What it is?

In short, **not what you think**.
In fact, it is **not** a new interface to fetch data, as that job is still delegated to libraries like `fetch` or `axios`, but instead it lets you integrate those libraries with React, and it's real job is to just say "show this code while is loading, and show that when it's done", nothing more than that.

## But how does it work?

Well, first you need to understand how a Promise work, and what are its states.
Regardless of how you consume a promise, if you use the traditional `new Promise()` or the new `async/await` syntax, a promise has always these three states:

- `pending` -> It's still processing the request
- `resolved`-> The request has returned some data and we got a 200 OK status
- `rejected` -> Something went wrong and we got an error

The logic used by `Suspense` is literally the opposite of `ErrorBoundary`, so if my code is throwing an exception, because it's either still loading or because it failed, show the fallback, if instead it did resolve successfully, show the children components.

## Let's see a practica example

Here I'm going to show a simple example, where we are simply going to have a component that needs to fetch some data from an API, and we just want to render our component once it's ready.

> **Note** 
> For simplicity's sake, I'm going to try to keep thing simple, I'm not going to mention how to use `startTransition`, or adding Error Boundary, or even the difference between the various strategies like "fetch-on-render", "fetch-then-render", etc... This article is already long enough, but if you want to know about those, please read [this article](https://17.reactjs.org/docs/concurrent-mode-suspense.html#what-suspense-is-not).

### Wrap your fetching logic!

As we said above, we need to throw an exception when our components is loading the data or it failed, but then simply return the response once the promise is resolved successfully.
To do that we'll need to wrap our request with this function:

```javascript
// wrapPromise.js
/**
 * Wraps a promise so it can be used with React Suspense
 * @param {Promise} promise The promise to process
 * @returns {Object} A response object compatible with Suspense
 */
function wrapPromise(promise) {
  let status = 'pending';
  let response;

  const suspender = promise.then(
    res => {
      status = 'success';
      response = res;
    },
    err => {
      status = 'error';
      response = err;
    },
  );

  const handler = {
    pending: () => {
      throw suspender;
    },
    error: () => {
      throw response;
    },
    default: () => response,
  };

  const read = () => {
    const result = handler[status] ? handler[status]() : handler.default();
    return result;
  };

  return { read };
}

export default wrapPromise;
```

So the code above will check our promise's state, then return a function called `read` which we'll invoke later in the component.

Now we'll need to wrap our fetching library with it, in my case `axios`, in a very simple function:

```javascript
//fetchData.js
import axios from 'axios';
import wrapPromise from './wrapPromise';

/**
 * Wrap Axios Request with the wrapPromise function
 * @param {string} url Url to fetch
 * @returns {Promise} A wrapped promise
 */
function fetchData(url) {
  const promise = axios.get(url).then(({data}) => data);

  return wrapPromise(promise);
}

export default fetchData;
```

The above is just an abstraction of our fetching library, and I want to stress that this is just a very simple implementation, all the code above can be extended to whatever you need to do with your data. I'm using `axios` here, but you could use anything you like.

### Read the data in the component

Once everything is wrapped up on the fetching side of things, we want to use it in our component!
So, let's say we have a simple component that just read a list of names from some endpoint, and we print them as a list.
And unlike how we did in the past, where we call the fetching inside the component in a `useEffect` hook, with something that it will look like [this example](https://dev.to/darkmavis1980/fetching-data-with-react-hooks-and-axios-114h), this time we want to call the request, using the `read` method we exported in the wrapper, right at the beginning of the component, outside any hooks, so our `Names` component will start like this:

```javascript
// names.jsx
import React from 'react';
import fetchData from '../../api/fetchData.js';

const resource = fetchData('/sample.json');
const Names = () => {
  const namesList = resource.read();

  // rest of the code
}
```

What is happening here, is when we call the component, the `read()` function will start to throw exceptions until it's fully resolved, and when that happen it will continue with the rest of the code, in our case to render it.
So the full code for that component will be like this:

```javascript
// names.jsx
import React from 'react';
import fetchData from '../../api/fetchData.js';

const resource = fetchData('/sample.json');

const Names = () => {
  const namesList = resource.read();

  return (
    <div>
      <h2>List of names</h2>
      <ul>
        {namesList.map(item => (
          <li key={item.id}>
            {item.name}
          </li>))}
      </ul>
    </div>
  );
};

export default Names;
```

### The parent component

Now is here were `Suspense` will come into play, in the parent component, and the very first thing to do is import it:

```javascript
// parent.jsx
import React, { Suspense } from 'react';
import Names from './names';

const Home = () => (
  <div>
    <Suspense fallback={<p>Loading...</p>}>
      <Names />
    </Suspense>
  </div>
);

export default Home;
```

**So what's happening there?**
We imported `Suspense` as a react component, then we use to wrap our component that is fetching the data, and until that data is resolved, it will just render the `fallback` component, so just the `<p>Loading...</p>`, and you can replace with your custom component if you wish so.

## Conclusions

After a long time using `useEffect` for achieving the same results, I was a bit skeptical of this new approach when I first saw it, and the whole wrapping of fetching library was a bit off-putting to be honest. But now I can see the benefits of it, and it makes very easy to handle loading states, it abstract some code away which it makes easier to reuse and it simplify the code of the component itself by getting rid (well, in most of the cases at least) the `useEffect` hook, which gave me a few headaches in the past.
I also recommend to watch [this video](https://www.youtube.com/watch?v=j8s01ThR7bQ) from @jherr which really helped me understanding the concept.

> Btw, if you want to see how to achieve the same results with less code thanks to SWR, please also read [this article](https://dev.to/darkmavis1980/using-swr-hook-with-suspense-in-react-18-1clk).