Data Fetching with React Suspense
Tags: react, javascript, webdev, tutorial
Published: 2023-08-27 13:35:04

Introduction

* What is React suspense?
    
* Importance of data fetching in react apps
    
* Fetching Data with React Suspense
    
* Data Fetching in React Suspense with custom hooks
    
* Example and How to create custom Hook
    
* Error Boundaries
    
* Importance of handling errors
    
* Creating Error Boundary Components
    
* Integrating Error Boundaries with Suspense Fallbacks
    
* Example Code
    
* Real world use cases
    
* 1\. Fetch on Render
    
* 2\. Fetch-then-Render
    
* 3\. Render-as-you-Fetch
    
* Implementing Pagination with React Suspense
    
* Adding Filter Functionality
    
* Example
    
* Best Practices and Performance Considerations
    
* Disabling Suspense for slow networks
    
* Code Splitting
    
* Data-caching strategies
    
* Profiling and optimization
    
* React Suspense and Concurrent UI
    
* What is the Concurrent Mode?
    
* Advantages of using Concurrent UI with React Suspense
    
* Example
    
* Conclusion
    

## Introduction

React Suspense is a built in react component that lets you display a fallback until its children have finished loading

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

React Suspense simplifies the handling of asynchronous data fetching and rendering in react apps thus helping the developer to enable them to declaratively specify loading states, error handling.

For almost all web and mobile applications, one of the most important aspects if data fetching from a remote server.

In React applications typically we have components that fetch the data by using API, then after fetching they process the data and after the processing is done the data is rendered on the screen

React Suspense provides a more intuitive and easy to maintain way to fetch and render data on screen.

Here is a simple example of React Suspense in action

1. We are creating a custom hook to fetch the data from the server. We are calling it `useDataFetch` this
    

```javascript
import { useState, useEffect } from "react";

function useDataFetch(url) {
  const [data, setData] = useState(null);
  const [searching, setSearching] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    async function getData() {
      try {
        const response = await fetch(url);
        const resultantData = await response.json();
        setData(resultantData);
      } catch (err) {
        setError(err);
      } finally {
        setSearching(false);
      }
    }
    getData();
  }, [url]);

  return { data, searching, error };
}
```

2\. We are then using the data returned by the `useDataFetch` hook with React Suspense

```javascript
import React, { Suspense } from 'react';
import { useFetch } from './useFetch';

function UserProfile({ userId }) {
  const { data, loading, error } = useFetch(`https://jsonplaceholder.typicode.com/users/${userId}`);

  if (loading) {
    throw new Promise(resolve => setTimeout(resolve, 1000));
  }

  if (error) {
    throw error;
  }

  return <div>{data.name}</div>;
}

function App({ userId }) {
  return (
    <div>
      <Suspense fallback={<div>Getting User Data </div>}>
        <UserProfile userId={userId} />
      </Suspense>
    </div>
  );
}
```

In this example we have a UserProfile Component that is getting the data from the `useDataFetch` custom hook

## Fetching Data with React Suspense and Custom Hooks

* To Fetch data using React Suspense we need to create custom hooks.
    
* These custom hooks will allow us to suspend the components while the data is retrieved
    
* React Suspense streamlines the loading states and error boundaries and makes for a more modular and declarative approach to data fetching in React suspense
    

Let us consider an example to better understand this

1. Let us consider an Object with a name resource, which is responsible for data catching and suspense integration
    

```javascript
const resource = {
  data: null,
  promise: null,
};
```

*resource Object*

2\. Now, we need to create a function that would update the resource object whenever it gets the data

```javascript
const fetchData = async (url) => {
  resource.promise = fetch(url)
    .then((response) => response.json())
    .then((data) => {
      resource.data = data;
      resource.promise = null;
    });

  throw resource.promise;
};
```

*fetchData function*

3\. Lastly we create a custom hook that we will integrate with React Suspense

```javascript
import { useState, useEffect } from "react";

function useDataFetch(url) {
  const [data, setData] = useState(resource.data);

  useEffect(() => {
    if (!resource.promise) {
      fetchData(url);
    }

    let promise = resource.promise;

    promise
      .then((data) => setData(data))
      .catch((error) => {
       //take care of the error here
      });
  }, [url]);

  if (resource.data) {
    return data;
  } else {
    throw resource.promise;
  }
}
```

When the `useDataFetch` hook is called the component will stop rendering on the screen and then only when the data is available the compoenent will start rendering again

## Integrating Custom Hooks with Suspense

In this section we will integrate the custom react hook that we created called the `useFetch` into our component

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

function Post({ id }) {
  const post = useFetch(`https://jsonplaceholder.typicode.com/posts/${id}`);

  return (
    <div>
      <h2>{post.title}</h2>
      <p>{post.body}</p>
    </div>
  );
}

function App() {
  return (
    <div>
      <h1>Getting the data from React</h1>
      <Suspense fallback={<div>It is taking some time to get the data</div>}>
        <Post id={1} />
      </Suspense>
    </div>
  );
}
```

### Error Boundaries: A Simple Example

The errors that happen in the front-end applications need to be handled gracefully.

Creating an error boundary component allows the developer to catch any errors thrown by the suspended component

let us look at an example to understand this better

```javascript
import React from "react";

class ErrorBoundary extends React.Component {
  state = { error: null };

  static getDerivedStateFromError(error) {
    return { error };
  }

  componentDidCatch(error, errorInfo) {
    // you can handle the errors here
  }

  render() {
    const { error } = this.state;
    const { children, fallback } = this.props;

    if (error) {
      return fallback ? fallback(error) : null;
    }

    return children;
  }
}
```

We need to wrap the components that are likely to throw errors inside the `ErrorBoundary` component that we created to it to be able to catch the errors

```javascript
function App() {
  return (
    <div>
      <h1>Fetching Data with React Suspense</h1>
      <ErrorBoundary fallback={(error) => <div>Error: {error.message}</div>}>
        <Suspense fallback={<div>Loading post...</div>}>
          <Post id={1} />
        </Suspense>
      </ErrorBoundary>
    </div>
  );
}
```

* Fetching data with react suspense takes creating custom hooks that fetch data and integrating them with components
    
* These components then suspended during data fetching.
    
* ErrorBoundries can be deployed to catch any error which might be thrown by the suspended components
    

## Why Error handling is important

We need to application to perform even if problems come during run time.

* We are looking to avoid issues like crashing, nondescriptive error messages, or unexpected behavior that would really frustrate the user
    
* This article is brought to you by DeadSimpleChat, [Chat API](https://deadsimplechat.com/) and SDK for your website and app
    
* Better error handling results in developers being able to quickly resolve issues and users getting relevant messages as to why the app is not working if it has stopped working.
    
* Enhances user experiences and makes the application more robust
    

## Creating error-handling boundary components

`ErrorBoundry` components catch errors anywhere in its child components, logs those errors and show the fallback UI

Let us consider an example of how to create an error boundary component

```javascript
import { useState, useEffect } from 'react';

function useErrorBoundary() {
  const [error, setError] = useState(null);

  const handleError = (err) => {
    setError(err);
  };

  useEffect(
    () =>
      function cleanup() {
        setError(null);
      },
    []
  );

  return [error, handleError];
}
```

Here we are creating an error boundary hook, next We will create a `ErrorBoundry` component below

```javascript
import React from 'react';
import { useErrorBoundary } from './useErrorBoundary';

function ErrorBoundary({ children, fallback }) {
  const [error, handleError] = useErrorBoundary();

  if (error) {
    return fallback ? fallback(error) : null;
  }

  return (
    <React.ErrorBoundary onError={handleError}>{children}</React.ErrorBoundary>
  );
}
```

## Integrating the Error boundry Hook with the react component and react suspense fallback

In this case, if the component fails then the suspense fallback will display an alternate UI to the user

```javascript
import React, { Suspense } from 'react';
import { useFetch } from './useFetch';
import ErrorBoundary from './ErrorBoundary';

function UserProfile({ userId }) {
  const { data, error } = useFetch(`https://api.example.com/users/${userId}`);

  if (error) {
    throw error;
  }

  return <div>{data.name}</div>;
}

function App() {
  return (
    <div>
      <h1>Data Fetching with React Suspense</h1>
      <ErrorBoundary fallback={(error) => <div>Error: {error.message}</div>}>
        <Suspense fallback={<div>Loading user profile...</div>}>
          <UserProfile userId={1} />
        </Suspense>
      </ErrorBoundary>
    </div>
  );
}
```

In the above example

* The `userProfile` component has the job of fetching the data by using the `useFetch` custom hook
    
* If there is some error in fetching the data then the suspended component throws an error.
    
* Which is caught by the `ErrorBoundry` component and the suspense fallback display the alternate UI
    
* This leads to better user experience and developer experience as well
    

## Real World use-cases

### Data fetch and render patterns

1. Fetch on render
    
2. fetch then render
    
3. render as you fetch
    

### Fetch-On-Render

In fetch on render, the component renders placeholder and loading states while requesting the data as they mount.

Data fetching starts as soon as the component is rendered and is blocked (that is placeholders are shown) until the data arrives

Let us look at the example

```javascript
import React, { useState, useEffect } from "react";

function BlogPost({ postId }) {
  const [post, setPost] = useState(null);

  useEffect(() => {
    (async () => {
      const response = await fetch(
        `https://jsonplaceholder.typicode.com/posts/${postId}`
      );
      const postData = await response.json();
      setPost(postData);
    })();
  }, [postId]);

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

  return (
    <div>
      <h2>{post.title}</h2>
      <p>{post.body}</p>
    </div>
  );
}
```

2\. **Fetch-then-render**

In this render pattern, the component is only shown when all the data is available for render

here no placeholder or loading state is shown, this may cause initial render times to slow down

But it is preferable for some kinds of use cases

```javascript
import React, { useState, useEffect } from "react";

function BlogPosts() {
  const [posts, setPosts] = useState(null);

  useEffect(() => {
    (async () => {
      const response = await fetch(
        "https://jsonplaceholder.typicode.com/posts?_limit=10"
      );
      const postData = await response.json();
      setPosts(postData);
    })();
  }, []);

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

  return (
    <div>
      {posts.map((post) => (
        <div key={post.id}>
          <h2>{post.title}</h2>
          <p>{post.body}</p>
        </div>
      ))}
    </div>
  );
}
```

### 3\. Render as you fetch

Here the data is rendered as soon as it is coming from the server. Here the data fetching and rendering are occurring at the same time

As soon as some of the data is available it is rendered by the component while waiting for the additional data from the server

This technique combines React Suspense with the custom hooks to better manage the async process

```javascript
import React, { useState, useEffect } from "react";

function useFetch(url) {
  const [data, setData] = useState(null);
  useEffect(() => {
    setData(null);
    fetch(url)
      .then((response) => response.json())
      .then((result) => {
        setData(result);
      });
  }, [url]);

  if (data === null) {
    throw new Promise((resolve) => setTimeout(resolve, 2000));
  }

  return data;
}

function BlogPost({ postId }) {
  const post = useFetch(
    `https://jsonplaceholder.typicode.com/posts/${postId}`
  );

  return (
    <div>
      <h2>{post.title}</h2>
      <p>{post.body}</p>
    </div>
  );
}

function App() {
  return (
    <div>
      <h1>Render as you fetch</h1>
      <React.Suspense fallback={<div>please wait while we are searching</div>}>
        <BlogPost postId={1} />
      </React.Suspense>
    </div>
  );
}
```

These are some of the real-world fetching patterns available in react suspense

You can use them according to the nature of your react application and the amount of data.

## Implementing Pagination and filtering for React Suspense

Let us learn how to implement pagination and filtering with react suspense

We are going to combine custom hooks and react suspense components to implment pagination

### Pagination

Let us create a custom hook named `usePaginatedFetch`  which will retrive the data based of the below parameters

`URL` and

`page`

```javascript
import { useState, useEffect } from "react";

function usePaginatedFetch(url, page) {
  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    setLoading(true);

    fetch(`${url}?_page=${page}&_limit=10`)
      .then((response) => response.json())
      .then((result) => {
        setData(result);
        setLoading(false);
      });
  }, [url, page]);

  if (loading) {
    throw new Promise((resolve) => setTimeout(resolve, 2000));
  }

  return data;
}
```

Next, we will create a new component and name that `Posts` component then we will use the `usePaginatedFetch` component to display the data

```javascript
import React, { Suspense, useState } from "react";
import { usePaginatedFetch } from "./usePaginatedFetch";

function Posts({ page }) {
  const posts = usePaginatedFetch(
    "https://jsonplaceholder.typicode.com/posts",
    page
  );

  return (
    <div>
      {posts.map((post) => (
        <div key={post.id}>
          <h2>{post.title}</h2>
          <p>{post.body}</p>
        </div>
      ))}
    </div>
  );
}

function App() {
  const [page, setPage] = useState(1);

  return (
    <div>
      <h1>React Suspense Pagination</h1>
      <Suspense fallback={<div>Please wait while we search for the page</div>}>
        <Posts page={page} />
      </Suspense>
      <button onClick={() => setPage((prevPage) => prevPage - 1)} disabled={page === 1}>
        older Page
      </button>
      <button onClick={() => setPage((prevPage) => prevPage + 1)}>Next Page</button>
    </div>
  );
}
```

Again let us revisit what we did in the above example

* We created a custom hook `usePaginatedData` which would fetch the data from the server using the params `URL` and page\`
    
* The state of the fetched data and the loading status are maintained by the custom hook
    
* In our code when the loading state is set to true then the react signals the component to skip rendering and wait for the data and the `usePaginatedFetch` sends a promise to fetch the data
    
* Then we created the `Posts` component that takes a `page` as a prop and renders a list of posts based on the data that is returned by the `usePaginatedData` custom hook. Data is fetched from the remote server
    
* And In the `App` Component we are wrapping the `Post` Component with Suspense so as to load the loading state
    
* We have also created 2 button components to handle pagination.
    

### Filters

Let us add the filter functionality. To do this we need to modify the `usePaginatedFetch` custom hook to accept a new `params` Object

Let us look at the code

```

function usePaginatedFetch(url, page, params) {
  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(true);

  const queryParams = new URLSearchParams({
    ...params,
    _page: page,
    _limit: 20,
  });
```

  useEffect(() => {
    setLoading(true);

    fetch(${url}?${queryParams})
      .then((response) => response.json())
      .then((result) => {
        setData(result);
        setLoading(false);
      });
  }, [url, page, params]);

  if (loading) {
    throw new Promise((resolve) => setTimeout(resolve, 2000));
  }

  return data;
}

```

Let us also update the `App` component to have an input field. that will filter the posts by the title

```javascript
function App() {
  const [page, setPage] = useState(1);
  const [searchTerm, setSearchTerm] = useState("");

  const handleSearch = (event) => {
    setSearchTerm(event.target.value);
    setPage(1);
  };

  return (
    <div>
      <h1>React Suspense with Pagination and Filters</h1>
      <input
        type="text"
        placeholder="Search by title..."
        value={searchTerm}
        onChange={handleSearch}
      />
      <Suspense fallback={<div>Loading...</div>}>
        <Posts page={page} params={{ q: searchTerm }} />
      </Suspense>
      <button
        onClick={() => setPage((prevPage) => prevPage - 1)}
        disabled={page === 1}
      >
        Previous
      </button>
      <button onClick={() => setPage((prevPage) => prevPage + 1)}>Next</button>
    </div>
  );
}
```

Let us consider what we are doing the above example

* We are modifying the `usePaginatedFetch` hook to accept a `params` Object
    
* In the `App` component we have added an input field to allow users to search for a term
    
* We have also created a `handleSearch` function to handle the `searchTerm` state whenever the input value changes that is whenever a user writes into the input field
    
* We are passing the `searchTerm` into the `params` props as a query parameter to the post component
    
* the `usePaginatedFetch` now includes the search term in the API request. This allows the server to filter the results using the search term
    

## Best Practices and Performance Considerations

1. Disabling Suspense for slow networks
    

* When the app is on a slow network, it might be better to show a loading screen rather than waiting for a fallback UI.
    
* You can disable suspense for slow networks by using the `navigator.connection` API to detect the user's network speed
    

```javascript
function isSlowNetwork() {
  return (
    navigator.connection &&
    (["slow-2g", "2g"].includes(navigator.connection.effectiveType) ||
      navigator.connection.saveData)
  );
}
```

When we get to know that the app is on a slow network then we can conditionally render the component without the `suspense` wrapper

```javascript
function App() {
  if (isSlowNetwork()) {
    return <Posts />;
  } else {
    return (
      <div>
        <h1>React Suspense tutorial</h1>
        <Suspense fallback={<div>Searching...</div>}>
          <Posts />
        </Suspense>
      </div>
    );
  }
}
```

### Code Splitting

* With code splitting, you can separate your application into smaller chunks.
    
* These chunks can be loaded only when they are needed thus saving on resources
    
* For example: loading the resources as the user navigates through the app
    
* For code splitting, you can use `React.lazy`
    

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

const Posts = lazy(() => import("./Posts"));

function App() {
  return (
    <div>
      <h1>React Suspense with Code Splitting</h1>
      <Suspense fallback={<div>Loading...</div>}>
        <Posts />
      </Suspense>
    </div>
  );
}
```

### Data-caching strategies

You can improve the performance of an application by caching the data fetched from the server.

### Profiling and Optimizing

Using React dev tools we can profile and optimize the application

We can check the component renders and find out the performance bottlenecks.

Thus we can optimize the app using the  React Dev tools

![](https://deadsimplechat.com/blog/content/images/2023/08/Screenshot_2-2-1.png align="left")

## Need [Chat API](https://deadsimplechat.com/) for your website or app

[DeadSimpleChat](https://deadsimplechat.com/) is a Chat API provider

* Add Scalable Chat to your app in minutes
    
* 10 Million Online Concurrent users
    
* 99.999% Uptime
    
* Moderation features
    
* 1-1 Chat
    
* Group Chat
    
* Fully Customizable
    
* Chat API and SDK
    
* Pre-Built Chat
    

## Conclusion

In this article we learnt about React Suspense and how we can use React suspense to fetch data by creating a custom hook

I hope you liked the article Thank you for reading