How to use Suspense for data fetching in React
Tags: react, productivity, programming, webdev
Published: 2022-10-27 08:40:51

`React.Suspense` lets you specify a loading indicator in case some components in the tree below it are not yet ready to render. It was introduced in **React v16.6**.

**Summary:** _In this article we explore usecases of `React.Suspense`. We go over in-depth how we can use `React.Suspense` with data fetching and implement it artfully to make faster applications_.

Let's see how we can use `React.Suspense` to write better React code and perfomant applications.

## Use cases of `React.Suspense`.

### 1. Lazy-loading usecase.

You almost probably have used `React.Suspense` when _lazy-loading_ components:

```jsx
import React from "react";
import Spinner from "react-loading-indicators";

const AdminPortal = React.lazy(() => import("./AdminPortal")); // Lazy-loaded
const RegularPortal = React.lazy(() => import("./RegularPortal")); // Lazy-loaded

const Dash = (props) => {
  const { user } = props;

  return (
    <React.Suspense fallback={<Spinner />}>
      {user === "admin" ? <AdminPortal /> : <RegularPortal />}
    </React.Suspense>;
  )
};

export default Dash;
```

From the snippet above, the lines:

```jsx
const AdminPortal = React.lazy(() => import("./AdminPortal")); // Lazy-loaded
const RegularPortal = React.lazy(() => import("./RegularPortal")); // Lazy-loaded
```

Means that we are _lazy loading components_. Using `React.lazy` enables us to render the _lazy components_ like _regular components_.
The _lazy component_ should be rendered inside a `<React.Suspense>` component, which allows us to show some fallback content (_such as a [loading indicator](https://www.npmjs.com/package/react-loading-indicators)_) while we are waiting for the _lazy component_ to load.

When user of our application is a _regular user(Not an `"admin"`)_, `<RegularPortal />` component will be rendered and **automatically** the bundle containing `RegularPortal` **is loaded**. `<AdminPortal />` component does not render and therefore the bundle containing `AdminPortal` **IS NOT loaded**.<br>
**On the other hand**, the logic is the same. If the user of our application is an _`"admin"`_, `<AdminPortal />` component will be rendered and **automatically** the bundle containing `AdminPortal` **is loaded**. `<RegularPortal />` component does not render and so the bundle containing `RegularPortal` **IS NOT loaded**.<br>
**The concept of _lazy loading_ is applied since we are waiting to load certain bundles until they are needed.**

We can _"lazy-load"_ because of _**Code-splitting**_ feature provided by build tools such as [webpack](https://webpack.js.org/guides/code-splitting/), where some pieces of code are bundled into separate files/bundles (_also known as chunk files_) rather than including it all in _one large main bundle_.

This helps reduce the size of the _main bundle_ and consequentially reduces initial page load time, initial page weight, and system resource usage when our application is served on the browser. _Performance of our application becomes dramatically improved_. We can then download the separate bundles(_chunk files_) only when the user action calls for that part of supporting bundle(_chunk file_).

**Lazy loading components is the one use case supported by `<React.Suspense>`**.

### 2. Data fetching usecase

Software developers are joined at the hip with **API calls**. Nevertheless, you need to know how to handle asynchronous requests in mighty frameworks like _React_ - _and in a good architectural point of view_.<br>
You can use `<React.Suspense>` for data fetching. In a good UX design, data fetching should be an _asynchronous operation_.

Before we can look at `<React.Suspense>` for data fetching, we will look at the traditional approach(_Fetch-on-render_), <i>for instance data fetching inside `useEffect`</i>

#### Traditional approach(_Fetch-on-render_).

In this approach:<br>
_Start rendering components_. _Each of these components may trigger data fetching in their effects and lifecycle methods_.

```jsx
import React, { useEffect, useState } from "react";
import Spinner from "react-loading-indicators";

import Post from "./Post";

function Posts() {
  const [isLoading, setIsLoading] = useState(true);
  const [posts, setPosts] = useState([]);

  useEffect(() => {
    setIsLoading(true);
    fetch("https://jsonplaceholder.typicode.com/posts")
      .then((response) => response.json())
      .then((json) => setPosts(json))
      .finally(() => setIsLoading(false));
  }, []);

  if (isLoading) {
    return <Spinner style={{ fontSize: "40px" }} color="black" />;
  }

  return (
    <div style={{ display: "flex", flexWrap: "wrap", gap: "10px" }}>
      {posts.map((post, idx) => (
        <Post post={post} key={idx} />
      ))}
    </div>
  );
}
```

We call this approach _**fetch-on-render**_ because it doesn't start **fetching** until after the component has rendered on the screen. _Remember useEffect runs after a component has rendered_.<br>
This leads to a _**"waterfall"** problem_, whereby _if we have **yet another `useEffect` in `<Post />` component(child) performing data fetching across network**, it would happen **after data fetching in `useEffect` of `<Posts />` component(parent) has run to completion**_. So if fetching data(posts) in `<Posts />` component takes _three seconds_, we’ll only fire request in `<Post />` component after _three seconds_!

#### Suspense approach(_Render-as-You-Fetch_).

Suspense for Data Fetching is a new feature. It lets you also use `<React.Suspense>` to declaratively _"wait"_ for anything else, including data. In this article, we focus on the data fetching use case, but it can also wait for images, scripts, or other asynchronous work.

With Suspense, **we don’t wait to render before we can start fetching**. In fact, **we kick off the network request(fetching) and immediately start rendering**:

```jsx
import React from "react";
import Post from "./Post";

// Variables we will use to replay state of fetch() promise to React
let status = "pending";
let result;

// Start fetching posts even before rendering begins
const userId = JSON.parse(localStorage.getItem("authenticatedUser"))?.id;
const postsData = fetchPosts(userId);

// Posts component (definition)
const Posts = () => {
  // No need for loading states
  const posts = postsData();
  return (
    <div style={{ display: "flex", flexWrap: "wrap", gap: "10px" }}>
      {posts.map((post, idx) => (
        <Post post={post} key={idx} />
      ))}
    </div>
  );
};

// Fetch external data
function fetchPosts(userId) {
  let url = `https://jsonplaceholder.typicode.com/posts${
    userId ? "?userId=" + userId : ""
  }`;
  let fetching = fetch(url)
    .then((res) => res.json())
    // Fetch request has gone well
    .then((success) => {
      status = "fulfilled";

      result = success;
    })
    // Fetch request has failed
    .catch((error) => {
      status = "rejected";

      result = error;
    });

  return () => {
    if (status === "pending") {
      throw fetching; // Suspend(A way to tell React data is still fetching)
    } else if (status === "rejected") {
      throw result; // Result is an error
    } else if (status === "fulfilled") {
      return result; // Result is a fulfilled promise
    }
  };
}

export default Posts;
```

In the above code, we are fetching posts from external source with a single line: `const postsData = fetchPosts(userId);`.<br>
**Inside `<Posts />` component**, we do not need to use _any loading states_ nor _fire a network request inside a useEffect_ - _like we did with **Traditional approach(Fetch-on-render)**_. We get our posts by writing `const posts = postsData();`. We should get our `posts` if `fetchPosts()` has finished fetching. If data fetching is not yet finished, React will not render this component(`<Posts />`) just yet.

_**NOTE how we start fetching before rendering begins**, that is, we start data fetching outside of the `<Posts />` component definition_. Fetching this way means that by the time react starts rendering `<Posts />` component, we are already fetching for our data(posts). Hence a crucial aspect of **Render-as-You-Fetch**. Speed of application is improved drastically.

`fetchPosts(id)` function fetches external data.<br>
We use [**Fetch API**](https://developer.mozilla.org/en-US/docs/Web/API/fetch) to fetch data from `url`.<br>
We declared global variables: `status` and `pending` which we use to _echo the state_ of the `fetch()` promise to React(whether it is _pending_, _fulfilled_ or _rejected_):

```jsx
const fetching = fetch(url)
  .then((res) => res.json())
  // Fetch request has gone well
  .then((success) => {
    status = "fulfilled";

    result = success;
  })
  // Fetch request has failed
  .catch((error) => {
    status = "rejected";

    result = error;
  });
```

We store the _fetch() promise_ in a `fetching` variable. And when the promise fulfills in the `.then()`, we shall _replay_ its fulfilled state to react by setting `status = "fulfilled";` and assign the response to `result` variable(`result = success;`).<br>
Also when the promise rejects in the `.catch()` block, we also _replay_ its rejected state to react by setting the associated variables.

In this piece of code:

```jsx
if (status === "pending") {
  throw fetching; // Suspend (A way to tell React we are still waiting for results)
} else if (status === "rejected") {
  throw result; // Echo the failed Fetch() promise to React
} else if (status === "fulfilled") {
  return result; // Echo fulfilled promise to React
}
```

We are simply just communicating the state of _fetch() promise_ to react, so that React can determine if the component is ready to be rendered.

`<Posts />` component can now be used with `React.Suspense`. We can render the `<Posts />` component like:

```jsx
import React from "react";
import Spinner from "react-loading-indicators";

import Posts from "./Posts";

// React Component to render
const MyPosts = () => {
  return (
    <div style={{ marginLeft: "20px" }}>
      <h1>My Posts</h1>
      {/* Wrap the <Posts /> component in React.Suspense to display a fallback(spinner)
      while the component is not yet ready to render (data is still fetching) */}

      <React.Suspense fallback={<Spinner style={{ fontSize: "40px" }} />}>
        <Posts />
      </React.Suspense>
    </div>
  );
};

export default MyPosts;
```

Here’s what happens when we render `<MyPosts />` on the screen:

- _We have fired the `fetchPosts()` data fetching_ when we imported `Posts` into `MyPosts`, that is `import Posts from "./Posts";`. This is so because in our `Posts` function snippet(<small><i>refer above</i></small>), `fetchPosts()` is **defined and called outside of the `<Posts />` component definition**.

- `<MyPosts />` component will be run and rendered from top to bottom. When React renders `<MyPosts />` component, it returns `<Posts />` component as children.

- React then runs `<Posts />` component. `<Posts />` component won't be rendered on the screen, if _`fetchPosts()` is still fetching data_. This component therefore _"suspends"_. And React will skip over it to try and render other components in the tree.

- There’s nothing left to try rendering. And because `<Posts />` suspended, React displays the closest `<React.Suspense>` fallback above it in the tree: `<Spinner style={{ fontSize: "40px" }} />`. That's it for this render.

**But we are not done**. Since `fetchPosts()` data fetching request fired, **more Data is streaming in**. React will **retry rendering**. When **data is finally fetched** by the `fetchPosts()` function, the `<Posts />` component will render successfully and we will no longer need the fallback component(`<Spinner style={{ fontSize: "40px" }} />`). Eventually, when data is successfully fetched, there will be no fallbacks on the screen.

## Why use `<React.Suspense>` for data fetching

- Avoid using loading states. We don't need to do checks - _if (isLoading) show `<Spinner />`; otherwise render `<Component />`_ - from our components. This removes boilerplate code.

- Simplifies making quick design changes. For example we may create a new `<DeletedPosts />` component and want to design `<Posts />` and `<DeletedPosts />` components to display on the web page at the same time even though they may be fetched from different endpoints hence finish fetching at different times. We could wrap them in a `<React.Suspense>` boundary like:

  ```jsx
  <React.Suspense fallback={<Spinner variant="split-disc" />}>
    <Posts />
    <DeletedPosts />
  </React.Suspense>
  ```

  Or in a different design, we may want `<Posts />` and `<DeletedPosts />` components to have different loading states. We simply wrap them in different independent `<React.Suspense>` boundaries like:

  ```jsx
  <React.Suspense fallback={<Spinner variant="split-disc" text="fetching posts..." />}>
    <Posts />
  </React.Suspense>
  <React.Suspense fallback={<Spinner style={{ fontSize: "40px" }} text="fetching deleted posts..." />}>
    <DeletedPosts />
  </React.Suspense>
  ```

- With **Render-as-you-fetch** aspect when implementing data fetching with `<React.Suspense>`, you get to create fast applications. We don’t want to delay data fetching across network until a component starts rendering.

- The `<React.Suspense>` approach feels more like reading data synchronously in a component — as if it were already loaded. Hence improving readability of code.

## Conclusion

In this article, we have looked at `<React.Suspense>` and how we can utilize it for **lazy-loading components** and **data-fetching**. Lazy-loading components useCase with `<React.Suspense>` is supported internally in React via `<React.lazy>`. We had to create custom patterns for data fetching useCase with `<React.Suspense>`.

Data fetching with `<React.Suspense>` has a major benefit of **creating fast applications** especially when you reiterate over _**Render-as-You-Fetch**_ aspect.

**Good to note:**<br>
As of version 18, React officially supports out of the box implementation of `React.Suspense` with _lazy-loading components_ through [`React.lazy`](https://reactjs.org/docs/code-splitting.html#reactlazy).<br>
For this reason, using `React.Suspense` to let components express that they are _"waiting"_ for data that is already being fetched, _custom patterns need to be crafted_. Using custom patterns may be a downside as implementation of `React.Suspense` may change in future.

In future, React plans to handle more scenarios using `React.Suspense` other than just _"lazy-loading"_ - [source](https://reactjs.org/docs/react-api.html#reactsuspense):

> In the future we plan to let Suspense handle more scenarios such as data fetching. You can read about this in our [roadmap](https://reactjs.org/blog/2018/11/27/react-16-roadmap.html).
>
> Today, lazy loading components is the only use case supported by `<React.Suspense>`

**That's a wrap!**<br>
Thanks for reading this far. I believe you found the article useful.<br>
You can find source code of what has been shared in this article [here](https://github.com/hane-smitter/react-suspense-data-fetching). Drop me a star🌟. Happy coding! 👨‍💻✨.

---

_Follow me on [twitter](https://twitter.com/smitterhane). You may want to catch up with other useful information I share_.