Async rendering in React: Suspense, Hooks, and other methods
Tags: react, webdev
Published: 2023-11-22 16:08:52

**Written by [Yomi Eluwande](https://blog.logrocket.com/author/yeluwande/)✏️**

Async operations are common in modern web applications. Fetching data from an API, loading large components, or running computational tasks are all examples of asynchronous code that take some time to complete. In React, rendering components asynchronously can improve perceived performance by allowing certain parts of the UI to render immediately, while other parts wait on async operations. 

React 18 introduced a powerful new feature called Suspense that allows components to "suspend" rendering while async logic is pending. When used correctly, Suspense enables coordinated asynchronous rendering across the component tree. 

Before diving into how React Suspense works, let's first understand the basics of functional components and how to make them asynchronous using React Suspense.

## Understanding async React components

Functional components have become the cornerstone of React development. They are JavaScript functions that return JSX, defining what should be rendered on the screen. They are concise, easy to test, and have gained immense popularity due to the introduction of Hooks. 

React Suspense provides a straightforward way to make functional components asynchronous. It allows you to define which parts of a component should suspend rendering until async operations, like data fetching, are complete. This ensures that your UI remains responsive and that the user experience is seamless. 

In functional components, data fetching and other async logic are usually performed inside the `useEffect` Hook. The problem is that this method blocks the entire component tree, even sections that don't depend on the async data. 

React Suspense provides a straightforward way to fix this, thereby making functional components asynchronous. It allows you to define which parts of a component should suspend rendering until async operations are complete. This ensures that your UI remains responsive and that the user experience is seamless.

## Implementing React Suspense for async rendering

Now that we understand the basics of functional components, async rendering, and Suspense, let's dive into the practical implementation of React Suspense for async rendering. 

To use React Suspense, you need to set it up in your project. This includes importing the necessary components and wrapping your application in a Suspense boundary. This setup step is crucial for making async rendering work seamlessly:

```javascript
<Suspense fallback={<Loading />}>
  <SomeComponent />
</Suspense>
```

Let’s see how Suspense is set up in a React project. Install the latest version of React using the command below:

```bash
npm install react@latest react-dom@latest
```

The latest stable version of React (18.2.0) features the `Suspense` component, which has the following props:

*   `children`: The intended UI that you wish to display will render. If any child components pause during rendering, the Suspense boundary will transition to displaying the fallback content
*   `fallback`: An alternative user interface that can be displayed instead of the primary UI when it is not yet fully loaded. Any valid React node can be used as a fallback, but typically, it is a minimal placeholder view, such as a loading spinner or [skeleton screen](https://blog.logrocket.com/build-skeleton-screen-css/). When child components pause during rendering, Suspense will automatically switch to displaying the fallback UI, and revert to the child components once the required data is available

Let’s start with a basic example of using Suspense to fetch a list of TV shows from an API. For the purposes of this example, [here’s a GitHub repository](https://github.com/yomete/async-rendering-suspense/tree/main) containing the source code. 

We’re interested in three files in this codebase:

*   `src/components/Shows/index.js`
*   `src/components/fetchShows.js`
*   `src/App.js`

Let’s start with the `src/components/Shows/index.js` file:

```javascript
import { fetchShows } from "../fetchShows";
import * as Styles from "./styles";

const resource = fetchShows();

const formatScore = (number) => {
  return Math.round(number * 100);
};
const Shows = () => {
  const shows = resource.read();

  return (
    <Styles.Root>
      <Styles.Container>
        {shows.map((show, index) => (
          <Styles.ShowWrapper key={index}>
            <Styles.ImageWrapper>
              <img
                src={show.show.image ? show.show.image.original : ""}
                alt="Show Poster"
              />
            </Styles.ImageWrapper>
            <Styles.TextWrapper>
              <Styles.Title>{show.show.name}</Styles.Title>
              <Styles.Subtitle>
                Score: {formatScore(show.score)}
              </Styles.Subtitle>
              <Styles.Subtitle>Status: {show.show.status}</Styles.Subtitle>
              <Styles.Subtitle>
                Network: {show.show.network ? show.show.network.name : "N/A"}
              </Styles.Subtitle>
            </Styles.TextWrapper>
          </Styles.ShowWrapper>
        ))}
      </Styles.Container>
    </Styles.Root>
  );
};
export default Shows;
```

Inside the `Shows` component, the `fetchShows` function is called to create a `resource` object that is used to fetch the list of TV shows. We wrote about the `resource` object and how it works in a [previous article here](https://blog.logrocket.com/data-fetching-react-suspense/#:~:text=The%20wrapPromise.js%20function%20has%20the%20following%20requirements%3A). 

Back to the `Shows` component — we use the `resource.read()` method to fetch the list of TV shows and assign it to the `shows` variable, which is then iterated over and used to display all the fetched shows in JSX. 

Finally, in the `App.js` file, to asynchronously render the list of TV shows, we wrap the `Shows` component in the `Suspense` element with a fallback that displays **loading...**:

```javascript
import React, { Suspense } from "react";
import "./App.css";
import Shows from "./components/Shows";

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <h1 className="App-title">React Suspense Demo</h1>
      </header>
      <Suspense fallback={<p>loading...</p>}>
        <Shows />
      </Suspense>
    </div>
  );
}
export default App;
```

Essentially, the `Shows` component is being suspended while the TV shows are being fetched. React utilizes the nearest Suspense boundary to display the fallback, which is ideally a loading component (in our case, a simple **loading…** text) until it is prepared to render.

After the data has been loaded, React conceals the loading fallback and proceeds to render the `Shows` component with the retrieved data.

## Using Suspense to reveal content all at once

One of the key benefits of React Suspense is the ability to reveal content all at once when all async operations are complete. This eliminates the waterfall effect where parts of the UI load gradually. Instead, the entire component becomes visible only when it's ready. 

Let’s have a look at how this can be implemented with an example. Again, this is available on the repository at the `content-together-at-once` branch [here](https://github.com/yomete/async-rendering-suspense/tree/content-together-at-once). 

Using the previous example as a starting position, the `src/components/fetchShows.js` file has been renamed to `src/components/fetchData.js` and now looks like this:

```javascript
import axios from "axios";

export const fetchData = (apiURL, artificialDelay) => {
  let status = "pending";
  let result;
  let suspender = new Promise((resolve, reject) => {
    setTimeout(() => {
      axios(apiURL)
        .then((r) => {
          status = "success";
          result = r.data;
          resolve();
        })
        .catch((e) => {
          status = "error";
          result = e;
          reject();
        });
    }, artificialDelay);
  });
  return {
    read() {
      if (status === "pending") {
        throw suspender;
      } else if (status === "error") {
        throw result;
      } else if (status === "success") {
        return result;
      }
    },
  };
};
```

This has now been re-purposed from exclusively fetching TV shows to fetching any kind of data as we now accept the API URL as an argument. We also have a new argument: `artificialDelay`, which is the amount of time in milliseconds that the function will wait before making the request. This will be useful later for adding an artificial delay to fetching external data from APIs. 

In the previous example, we used TV Maze’s API to search for shows that contain `heist` in their title. Now for this example, which will involve fetching multiple content, we’ll fetch the details about a particular show (_Money Heist_, or _La Casa de Papel_) in one component and all of its episodes in another component. Then, we’ll place both components inside the Suspense boundary and see how Suspense can be used to reveal content all at once when all async operations are complete. 

The `src/components/ShowDetails/index.js` [file](https://github.com/yomete/async-rendering-suspense/blob/content-together-at-once/src/components/ShowDetails/index.js) contains the `ShowDetails` component, which is used to fetch details about the show. The `src/components/ShowEpisodes/index.js` [file](https://github.com/yomete/async-rendering-suspense/blob/content-together-at-once/src/components/ShowEpisodes/index.js) contains the `ShowEpisodes` component, which is used to fetch all of the show’s episodes. 

Additionally, the `ShowEpisodes` component has been set up in a way that there is a five second delay in addition to the time it takes for the API request to be completed:

```javascript
const resource = fetchData(`https://api.tvmaze.com/shows/27436/episodes`, 5000);
```

The `App.js` file also now looks like this:

```javascript
import React, { Suspense } from "react";
import "./App.css";

import ShowDetails from "./components/ShowDetails";
import ShowEpisodes from "./components/ShowEpisodes";

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <h1 className="App-title">React Suspense Demo</h1>
      </header>
      <Suspense fallback={<p>loading...</p>}>
        <ShowDetails />
        <ShowEpisodes />
      </Suspense>
    </div>
  );
}

export default App;
```

With this, the whole tree inside Suspense is treated as a single unit. So in our case, even though the `ShowDetails` component will be done loading before `ShowEpisodes`, the loading indicator will still be displayed until all components inside the Suspense boundary are done with their operations: ![React Suspense Demo Loading Show Information All At Once](https://blog.logrocket.com/wp-content/uploads/2023/11/react-suspense-demo-loading-shows.gif)

## Using Suspense to reveal nested content as it loads

React Suspense isn't limited to top-level components. You can use it to reveal nested content as it loads, ensuring that your UI remains responsive and the user is never left waiting. Let’s have a look at how this can be implemented with an example. Again, this is available on the repository at the `nested-content` branch [here](https://github.com/yomete/async-rendering-suspense/tree/nested-content). 

We’ll be working with the `ShowDetails` and `ShowEpisodes` components again but with a few changes to demonstrate how Suspense can be used to reveal nested components as it loads. 

The first change is in the [`ShowDetails` component](https://github.com/yomete/async-rendering-suspense/blob/nested-content/src/components/ShowDetails/index.js#L37), where we now directly import and utilize the `ShowEpisodes` component as opposed to the previous example where it was being utilized in the `App.js` file:

```javascript
import React, { Suspense } from "react";
import { fetchData } from "../fetchData";
import * as Styles from "./styles";
import ShowEpisodes from "../ShowEpisodes";
const resource = fetchData(`https://api.tvmaze.com/shows/27436`);

const removeTags = (str) => {
  if (str === null || str === "") return false;
  else str = str.toString();
  return str.replace(/(<([^>]+)>)/gi, "");
};

const Loading = ({ name }) => (
  <Styles.Loading>
    <p>loading episodes for {name}...</p>
  </Styles.Loading>
);

const ShowDetails = () => {
  const show = resource.read();

  return (
    <Styles.Root>
      <Styles.Container>
        <div>
          <img src={show.image.medium} alt="show poster" />
          <p>Show name: {show.name}</p>
          <p>Description: {removeTags(show.summary)}</p>
          <p>Language: {show.language}</p>
          <p>Genres: {show.genres.join(", ")}</p>
          <p>Score: {show.rating.average}/10</p>
          <p>Status: {show.status}</p>
        </div>

        <Suspense fallback={<Loading name={show.name} />}>
          <ShowEpisodes />
        </Suspense>
      </Styles.Container>
    </Styles.Root>
  );
};
export default ShowDetails;
```

By making this modification, the `ShowDetails` component no longer requires to wait for the `ShowEpisodes` component to complete its loading process to be displayed. 

The `App.js` file also now looks like this:

```javascript
import React, { Suspense } from "react";
import "./App.css";

import ShowDetails from "./components/ShowDetails";

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <h1 className="App-title">React Suspense Demo</h1>
      </header>

      <Suspense fallback={<p>loading...</p>}>
        <ShowDetails />
      </Suspense>
    </div>
  );
}
export default App;
```

With the changes above, the following sequence will occur:

*   If the `ShowDetails` component has not finished loading, the "loading..." indicator will be displayed instead of the entire content area
*   Once the `ShowDetails` component has finished loading, the "loading..." indicator will be replaced by the actual content
*   Within the `ShowDetails` component itself, if the `ShowEpisodes` component has not finished loading, the `<Loading name={show.name} />` component will be displayed in place of the `ShowEpisodes` component
*   Finally, once the `ShowEpisodes` component finishes loading, it will replace the `<Loading name={show.name} />` component

![React Suspense Demo Displaying Loaded Information One By One](https://blog.logrocket.com/wp-content/uploads/2023/11/react-suspense-demo-loaded-information.gif)

## Implementing React Suspense with `React.lazy()`

[`React.lazy()`](https://blog.logrocket.com/lazy-loading-components-in-react-16-6-6cea535c0b52/)is another feature that pairs seamlessly with React Suspense. It allows you to load components lazily, which is especially beneficial for optimizing performance in large applications. In this section, we'll explore how to use `React.lazy()` in conjunction with Suspense to supercharge your app's performance. 

The `React.lazy()` function enables the rendering of a dynamically imported component as a regular component. It simplifies the process of creating components that are loaded dynamically while being rendered like any other component. When the component is rendered, the bundle containing it is automatically loaded. 

A component created using `React.lazy()` is loaded only when it is needed to be shown. During the loading process of the lazy component, it is advisable to display placeholder content, such as a loading indicator. This is where Suspense can be utilized. 

When loading lazy components, you can display a loading indicator as placeholder content by providing a fallback prop to the suspense component. In essence, this allows you to specify a loading indicator that will be shown if the components within the tree below it are not yet prepared for rendering. 

A basic example can be seen in the `lazy-load` branch [here](https://github.com/yomete/async-rendering-suspense/tree/lazy-load). In the `App.js` file, the `Shows` component is lazy-loaded by being imported with the `React.lazy()` function and then wrapped in a Suspense boundary:

```javascript
import React, { Suspense } from "react";

import "./App.css";

const Shows = React.lazy(() => import("./components/Shows"));

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <h1 className="App-title">React Suspense Demo</h1>
      </header>
      <Suspense fallback={<p>loading...</p>}>
        <Shows />
      </Suspense>
    </div>
  );
}

export default App;
```

## Other async rendering methods: `useState` and `useEffect`

The `useEffect` and `useState` Hooks can be used together to perform asynchronous rendering in React apps. By combining [`useEffect`'s ability to perform side effects](https://blog.logrocket.com/useeffect-react-hook-complete-guide/#how-to-execute-side-effects-useeffect) and `useState`'s ability to keep track of a state value, you can mimic how Suspense asynchronously renders components. 

Let’s look at an example to better understand this. As a reminder, asynchronous rendering is essentially the ability to render components and update the user interface in a nonblocking manner, and this is what we’ll be demonstrating with this example. 

Just like previous examples, this example is available on GitHub under the [`use-effect-state`](https://github.com/yomete/async-rendering-suspense/tree/use-effect-state) branch. The `src/components/Shows/index.js` file has been edited now to look something like this:

```javascript
import React, { useState, useEffect } from "react";
import * as Styles from "./styles";

const formatScore = (number) => {
  return Math.round(number * 100);
};

const Shows = () => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    async function fetchData() {
      try {
        const result = await fetch(
          "https://api.tvmaze.com/search/shows?q=heist"
        );
        const data = await result.json();
        setData(data);
        setLoading(false);
      } catch (error) {
        setError(error);
        setLoading(false);
      }
    }
    fetchData();
  }, []);

  if (loading) {
    return <div>Loading...</div>;
  }

  if (error) {
    return <div>Error: {error.message}</div>;
  }

  return (....)
}
```

In the code above, three local states help us to asynchronously render the `Shows` component. The `data` state contains the result of the external request to TV Maze’s API, the `loading` state indicates whether the data is currently being fetched (`true`) or not (`false`), and the `error` state holds any error that occurs during the data fetching process. 

The `useEffect` Hook is used to fetch data from the TV shows API when the component mounts. It is an asynchronous function that uses `fetch` to make a GET request to the API endpoint. The fetched data is then stored in the `data` state using `setData`. 

The `loading` state is set to `false` to indicate that the data fetching process is complete and if an error occurs during the fetch, it is stored in the `error` state using `setError`. 

Now for rendering the actual component, the component renders different content based on the state:

*   If `loading` is `true`, it displays a loading message (`<div>Loading...</div>`)
*   If `error` is not `null`, it displays an error message with the error details
*   If neither `loading` nor `error` is true, it renders the TV show data

The above is quite similar to what was being done in earlier Suspense examples, albeit a bit more manually. We display the `Loading…` text when data is being fetched, and once it’s done, the actual TV shows data is rendered.

## Conclusion

Async rendering is a crucial aspect of modern web development, and React Suspense in React 18 has emerged as a powerful tool for managing asynchronous operations seamlessly. 

We've explored the fundamentals of React Suspense, and its practical implementation, and compared it with other async rendering methods. With the knowledge gained from this guide, you'll be well-equipped to make the most of async rendering and React Suspense in your next React project.

---

## Get set up with LogRocket's modern React error tracking in minutes:

1. Visit https://logrocket.com/signup/ to get an app ID.
2. Install LogRocket via NPM or script tag. `LogRocket.init()` must be called client-side, not server-side.

NPM:
```bash
$ npm i --save logrocket 

// Code:

import LogRocket from 'logrocket'; 
LogRocket.init('app/id');
```
Script Tag:
```javascript
Add to your HTML:

<script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
<script>window.LogRocket && window.LogRocket.init('app/id');</script>
```

3.(Optional) Install plugins for deeper integrations with your stack:
*   Redux middleware
*   ngrx middleware
*   Vuex plugin

[Get started now](https://lp.logrocket.com/blg/signup)