Make Your Website's User Experience Better with React Suspense
Tags: webdev, react, frontend, performance
Published: 2023-04-02 19:37:46

You've probably been in a situation where you visited a website and that site took a bit of time to load, you most likely had to sit down waiting as the loading animation repeated itself. Slow loading speeds are often due to the large amount of code or data the site is loading at once. Having to wait for the site to load could be extremely annoying, not to mention a waste of time.

Trying to navigate through the website as the website continued to load was probably not the best experience, and you probably had to wait until the browser had loaded all the code necessary for the site to function.

If you're a React developer, you can massively speed up site load time by indicating an order of render for components using `lazy()` and `<Suspense>`.

## React lazy()
`lazy()` is a function that relies on dynamic [import](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import) and reduces your website or web app load time by [code splitting](https://developer.mozilla.org/en-US/docs/Glossary/Code_splitting).

Lazy-loading a component tells the browser that the component isn't a priority, the browser will load all other components and load a lazy-loaded component only when a user navigates to that component or route.

Lazy loading offers several advantages, some of which are:

- Cut down resources needed to load a site
- Reduce the time it takes a user to interact with a site
- Improve a website's user experience

## Is Lazy loading necessary?
We'll answer this using an example. The code snippet below is from a React app with a `Navbar` component and two other components named `PageOne` and `PageTwo`

```
import PageOne from "./components/PageOne";
import PageTwo from "./components/PageTwo";
import { Routes, Route } from "react-router-dom";
import Navbar from "./components/Navbar";
function App() {
  return (
    <>
      <Navbar />
      <Routes>
        <Route path="/" element={<PageOne />} />
        <Route
          path="pagetwo" element={<PageTwo />}
        />
      </Routes>
    </>
  );
}

export default App;
```
The `PageOne` and `PageTwo` components contain two images each.

On initial load, we land on our index or home page shown in the image below


![Home page of a website showing two adorable cats](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4z463xlqy3w3a07qgklf.png)

I have not navigated to the Second Page/PageTwo component.

However, the browser has already loaded all the components in the background. To confirm this, I'll navigate to the Sources tab in the Dev tools of chrome browser(If you use Firefox this tab is called 'debugging')

![A screenshot of chrome's devtools with a red arrow pointing to the components file tree](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aufcj0rmfqd7zrmp5i78.png)

There's an arrow pointing to the components folder in the file tree of the React App on the left side of the image. You can see the browser has already loaded the PageTwo component even though I haven't navigated there yet. This could be an unnecessary drain of user resources, you might wonder, how?

Let's assume you're a content creator that has certain content available for paid users only, loading that content in the background for free users even though they can't access it would not make much sense.

Lazy-loading that premium content will fix this and prevent the browser from loading that content unless a user can access it.

## Getting Started with lazy()
Lazy-loading a component takes a different approach from the traditional import() method. I've modified the App component to demonstrate lazy loading
```
import { lazy, Suspense} from "react";
import { Routes, Route } from "react-router-dom";
import Navbar from "./components/Navbar";
import PageOne from "./components/PageOne";
const PageTwo = lazy(() => {import("./components/PageTwo")});

function App() {  
  return (
    <>
      <Navbar />
      <Routes>
        <Route path="/" element={<PageOne />} />
        <Route
          path="pagetwo"
          element={
            <Suspense fallback="Loading...">
              <PageTwo />
            </Suspense>
          }
        />
      </Routes>
    </>
  );
}

export default App;
```
Let's go over the modifications made to the App component

To lazy load a component, the first thing you need to do is import lazy and Suspense from React and you can see this below.
```
import { lazy, Suspense } from 'react'
```
The next step to take is to import your component in the lazy function block and assign it to a variable. You can see this in the example below. The PageTwo component is now being imported in the lazy function block and declared as a variable.
```
const PageTwo = lazy(() => {import("./components/PageTwo")});
```
Note - _Lazy imports should be done outside a function component and not inside. This means you have to declare them at the top with any other exports_

When you're done importing your lazy component, you need to wrap it in `<Suspense>`. A lazy-loaded component or route must be wrapped in `<Suspense>`

`<Suspense>` displays a fallback whenever a user navigates to a component and the component is being loaded. You'll learn more about `<Suspense>` and `fallback` as you read on

The `fallback` in `<Suspense>` is used to display any React component or text you want to render while your component loads. A `fallback` is important as it gives visual feedback that the component is loading.
```
<Route
          path="pagetwo"
          element={
            <Suspense fallback="Loading...">
              <PageTwo />
            </Suspense>
          }
        />
```
If you're wrapping a route component in `Suspense`, it has to be within the curly braces of the `Route element`.

The route has now been lazy-loaded and after reloading the React App the change is evident in the Dev tools

![A screenshot of chrome's devtools but this time a component has been lazy loaded and isn't loading unnecessarily](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tddx46zd2uofb4h37tb5.png)

Check it out, the `PageTwo` component was not loaded by the browser on initial render. The component will only be loaded when I navigate there. I've included an animated image below to show this in effect.

![An animated image showing how a component only loads when I navigate there and not when the website is loaded](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7lhs3tbj60apsewtd46l.gif)

The browser only loaded `PageTwo` when I navigated there and not on the initial render as it did before. This is a huge performance boost as the code and files loaded on the initial render are now smaller than before. Now that you've seen how good lazy-loading is, it's time to see what `<Suspense>` does.

## React Suspense

The initial explanation of `<Suspense>` was brief and in this section, you'll learn more about `<Suspense>`. You'll learn:

* How to use one `<Suspense>` for multiple components.
    
* How to use multiple `<Suspense>` for different components
    
* How to add an `ErrorBoundary` to `<Suspense>`
    

The `PageTwo` component used in the section discussing lazy loading contains a `<Suspense>` with a `fallback` with the text "Loading..." The image below shows what the suspense displayed while the component was loaded in the background


![A screenshot showing a site's homepage with a navbar and a loading text on screen](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/owrxbja04dh835d1p1aq.png)

A `<Suspense>` `fallback` would normally be a loader component with a [spinner](https://flowbite.com/docs/components/spinner/) or a [skeleton](https://flowbite.com/docs/components/skeleton/) loader, but we'll keep things simple in this project.

Something worth mentioning is that you can use `<Suspense>` on components that aren't lazy loaded.

### Wrap multiple elements in one suspense

You can wrap several components in a single `<Suspense>`. Multiple components wrapped in a `<Suspense>` will be loaded at the same time with a single `fallback`.

The code below has three components wrapped in a single `<Suspense>`.

```javascript
import LightComponent from "./components/LightComponent";
import { lazy, Suspense } from "react";
const MediumComponent = lazy(() =>(import("./components/MediumComponent")
));
const HeavyComponent = lazy(() => (import("./components/HeavyComponent")
));
function App() {
  return (
    <section>
      <Suspense fallback="Loading">
        <LightComponent />
        <MediumComponent />
        <HeavyComponent />
      </Suspense>
    </section>
  );
}

export default App;
```

There are three components of varying render times in the code snippet above, they are:

* `LightComponent` - Contains a simple text.
    
* `MediumComponent` - Contains an image.
    
* `HeavyComponent` - Contains an image.
    

All the components are in the same `<Suspense>` with a `fallback`. `MediumComponent` and `HeavyComponent` are lazy-loaded, You may have noticed that LightComponent isn't lazy-loaded but still works with `<Suspense>`. As I mentioned, you don't need to lazy load a component for it to work with `<Suspense>`

Placing all three components in the same `<Suspense>` means all three components will be completely loaded before rendering. The result is the image below


![Image of a black screen with a simple text of "Loading"](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mfjxtv9s7lpbltt02wb4.png)

One or all of the three components is not ready to render to the screen, this causes the `fallback` to be displayed until all components are ready. After fully loading all the components, it renders them all at once


![A homepage of a site showing two adorable cats](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u53icraszahdgdml2fna.png)

You can see from the above example that wrapping all your elements in one `<Suspense>` will load all your components at once, even if some of the components are already loaded and ready to render. The page will only display the components when everything is loaded and ready to render.

### Nesting suspense

You've seen that wrapping several elements in the same `<Suspense>` might not always be the best option. You can wrap your components in different `<Suspense>` if you have components with varying sizes.

Check the code snippet below.

```javascript
import LightComponent from "./components/LightComponent";
import { lazy, Suspense } from "react";
const MediumComponent = lazy(() =>(import("./components/MediumComponent")
));
const HeavyComponent = lazy(() => (import("./components/HeavyComponent")
));
function App() {
  return (
    <section>
      <Suspense>
        <Suspense fallback="Loading Light Component">
          <LightComponent />
        </Suspense>
        <Suspense fallback="Loading Medium Component">
          <MediumComponent />
        </Suspense>
        <Suspense fallback="Loading Heavy Component">
          <HeavyComponent />
        </Suspense>
      </Suspense>
    </section>
  );
}

export default App;
```

Notice how each component has its own `<Suspense>`. Wrapping each component with its `<Suspense>` means it will now be rendered in order of readiness. The order of render for our App would now be:

1. The entire `App Component` would start loading with a fallback of `Loading...`
    
2. `LightComponent` will get rendered to the screen immediately after it finishes loading.    

![Image of a black screen with simple text showing the sequence of loading of components, one components has rendered while the rest are still loading](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/alssaeq6x72zbstf66wb.png)
    
3. The fallbacks for `MediumComponent` and `HeavyComponent` will be displayed as those components continue fetching data.
    
4. `MediumComponent` will be rendered once fully loaded while the fallback for `HeavyComponent` remains as `HeavyComponent` keeps fetching data.
    
![Image of a black screen with simple text showing the sequence of loading of components, two components have rendered but the last one is still loading](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yquae4kkxj0u3kpj0cx6.png)
    
5. `HeavyComponent` gets rendered immediately after it fully loads. 

![All images have been fully rendered to the screen](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rl1palpmpbph93xqzvdv.png)    

You've seen how `<Suspense>` can greatly improve the quality of your websites or web apps. However, just because you can wrap multiple components in `<Suspense>` does not mean you should. You should wrap only heavy or lazy-loaded components in `<Suspense>`, Wrapping components with only texts or light images in suspense would not make much sense.

Some things you can wrap in `<Suspense>` are:

* A component that makes a call to an API
    
* A component with a lot of images
    
* Routes
    

> “Simplicity boils down to two steps: Identify the essential. Eliminate the rest.”  
> ― **Leo Babauta**

### Improve Suspense with an Error Boundary

You can level up your code even further by adding an error boundary to `<Suspense>`. An error boundary catches errors in your code that could otherwise break your app.

If a component wrapped in `<Suspense>` without an error boundary throws an error, your app will only display a blank page which wouldn't be very helpful. `<Suspense>` with an `<ErrorBoundary>`, however, will display a fallback instead of a blank page

### Add an error boundary

To add `<ErrorBoundary>` to `<Suspense>`, you need to install `react-error-boundary` as a dependency using either `npm` or `yarn` install.

Follow these steps to add `<ErrorBoundary>` to `<Suspense>`.

1. Navigate to your terminal and type `npm install react-error-boundary` if you use the node package manager or `yarn add react-error-boundary` if you use yarn.
    
2. Import `<ErrorBoundary>` into your app at the top
    
    ```javascript
    import { ErrorBoundary } from "react-error-boundary"
    ```
    
3. Wrap your suspense in `<ErrorBoundary>` and provide a fallback for `<ErrorBoundary>`. Using a code snippet from one of our earlier examples to demonstrate this, we have
    
    ```javascript
            <ErrorBoundary fallback={<p>Something went wrong</p>}>
                <Suspense fallback="Loading Heavy Component">
                  <HeavyComponent />
                </Suspense>
            </ErrorBoundary>
    ```
    
    The fallback makes sure you or your users can see an error message instead of a blank page, doing this greatly improves the user experience of your website.
    

You can go one step further to improve `ErrorBoundary` by adding an `ErrorFallback` component instead of a simple text, with an `ErrorFallback` component you can display a proper error message, together with a button to refresh the page or reset your app state. You can create an `ErrorFallback` by following these steps.

1. Create a new React component called `ErrorFallback` and in that component add the following code
    
    ```javascript
    const ErrorFallback = ({ error, resetErrorBoundary }) => {
      return (
        <div>
            <p>Something went wrong</p>
            <pre>{error.message}</pre>
            <button onClick={resetErrorBoundary}>Try again</button>
        </div>
      )
    }
    
    export default ErrorFallback
    ```
    
    Error Fallback takes in two props:
    
    `error` is the error thrown by the component wrapped in the error boundary.
    
    `resetErrorBoundary` is a function that resets your state or App to its original state before an error was thrown
    
2. Import the `ErrorFallback` component in your App component or wherever you used the error boundary
    
    ```javascript
    import ErrorFallback from "./components/ErrorFallback";
    ```
    
3. Replace the `fallback` prop in the error boundary with a new prop called `FallbackComponent`
    
    ```javascript
    <ErrorBoundary FallbackComponent={ErrorFallback} onReset={() => /*add your code to reset state here*/}> 
        <Suspense fallback="Loading Heavy Component"> 
            <HeavyComponent /> 
        </Suspense> 
    </ErrorBoundary>
    ```
    
    Add the `ErrorFallback` component to the `FallbackComponent`
    
4. Add a function called `onReset` to `ErrorBoundary` the `onReset` function takes in a code used to reset the state of the parent component to its original state before an error was thrown. It's up to you to provide the code
    

## Conclusion

In this article you have seen how to:

* Lazy-load components.
    
* Add `<Suspense>` to or Suspend your components.
    
* Add `fallback` to `<Suspense>`.
    
* Add `ErrorBoundary` to `<Suspense>` to catch any errors.
    

You've also seen that you need to add `<Suspense>` to lazy components, but you don't need to lazy load a component before using `<Suspense>`

Add `lazy()` and `<Suspense>` to appropriate sections of your code and you'll improve your website's user experience a lot. If you're interested in learning further, you can check the [official react docs](https://beta.reactjs.org/reference/react/Suspense).

If you have any comments or questions please feel free to ask them or reach out to me on [Twitter](https://twitter.com/human_eiffel) where I'm most active