How to Fetch Data with React Suspense

December 16, 2019

A while ago, React introduces a new set of features called "Concurrent Mode". And its basically allows you to interrupt the rendering process of your React components. And, with this feature comes the React Suspense.

Traditionally, data fetching in React looks like this:

import React from "react"

class Example extends React.Component {
	componentDidMount() {
		const data = fetchSomething() // Fetch data here
	}

	// ...
}

It's very obvious that the request happened whenever this component is being rendered or mounted. Suspense allows us to "wait" until we get the data that being fetched asynchronously before we render our component.

Even though they are not yet available in the stable release, we can still enjoy these features by using the experimental builds.

Getting Started

First, you need to have a React application. If you don't have one already, you can easily use create-react-app by running this command.

$ npx create-react-app learn-suspense

Once your project has been created, we need to install the experimental version of our react and react-dom package.

$ npm install react@experimental react-dom@experimental

I also have published the source code of this entire project on my GitHub. Go ahead and clone it into your computer.

$ git clone https://github.com/rahmanfadhil/learn-suspense.git

Enable concurrent mode

Before we get started using Suspense, we need to enable the concurrent mode first. We can do that by using the createRoot method to render our App component in the index.js file.

index.js
import React from "react"
import ReactDOM from "react-dom"
import App from "./App"

const element = document.getElementById("root")
ReactDOM.createRoot(element).render(<App />)

Fetch data

Now, let's create file called resource.js.

Here, we're going to write a new function to make an HTTP request. In this example, we're going to use JSONPlaceholder. An easy-to-use, open-source, fake REST API for testing front-end applications.

resource.js
async function fetchPosts() {
	const response = await fetch("https://jsonplaceholder.typicode.com/posts")
	const data = await response.json()
	return data
}

Here, we're using the JavaScript Fetch API to fetch some fake blog posts, and return the result.

We also need to create a function called createResource.

resource.js
// ...

export default function createResource() {
	let status = "loading"
	let result
	let suspender = fetchPosts().then(
		(data) => {
			status = "success"
			result = data
		},
		(error) => {
			status = "error"
			result = error
		}
	)

	return {
		read() {
			if (status === "loading") {
				throw suspender
			} else if (status === "error") {
				throw result
			} else if (status === "success") {
				return result
			}
		},
	}
}

We will use this function to display the blog posts in our React components. This allows us to tell React that we want to "wait" for our component gets rendered until we have done fetching data from the server.

So, when we execute this function, it will run the fetchPosts function where we fetch our blog posts. Then, whenever the promise is completed or rejected, we change the status of the status variable.

The read method will be used to get the data which will be displayed into the browser. When the promise is still pending, we need to throw the suspender variable, which contains our promise. If it gets rejected, we throw our error that lives inside the suspender variable. Finally, we just return the result when the data has fetched successfully.

Display fetched data

Now, let's create a component that will display our blog posts. We'll call it PostList component. Let's put it inside PostList.js file.

PostList.js
import React from "react"

export default function PostList({ resource }) {
	const posts = resource.read()

	return (
		<ul>
			{posts.map((post, i) => (
				<li key={i}>{post.title}</li>
			))}
		</ul>
	)
}

Here, we are accepting a resource props which contains our resource. Then, we display an unordered list and loop through the blog posts that we have just fetched.

Now in our App.js, we can use the PostList component.

App.js
import React, { Suspense } from "react"
import PostList from "./PostList"
import createResource from "./resource"

const resource = createResource()

export default function App() {
	return (
		<div>
			<h1>Blog Posts</h1>
			<Suspense fallback={<h1>Loading...</h1>}>
				<PostList resource={resource} />
			</Suspense>
		</div>
	)
}

Here, we're fetching the blog posts by executing the createResource function. Then, we render our PostList component and wrap it inside the Suspense component. This component takes a fallback prop, where we display the loading component.

We also need pass the resource into our PostList component, so that it can display the result.

Cool! we can finally test our app to make sure everything is running as expected.

loading

loaded

Fetch multiple data

There are a lot of cases where you want to fetch multiple data at once. But for now, we need to rewrite the createResource function over and over again to fetch a different endpoint or server.

We can prevent this by extracting the createResource function into something like this.

resource.js
// ...

function wrapPromise(promise) {
	let status = "loading"
	let result
	let suspender = promise.then(
		(data) => {
			status = "success"
			result = data
		},
		(error) => {
			status = "error"
			result = error
		}
	)

	return {
		read() {
			if (status === "loading") {
				throw suspender
			} else if (status === "error") {
				throw result
			} else if (status === "success") {
				return result
			}
		},
	}
}

export default function createResource() {
	return {
		posts: wrapPromise(fetchPosts()),
		// add here
	}
}

Now, with this approach, you can add more data into your resource by simply wrap your promises with wrapPromise function.

We also need to do some adjustment into your PostList component.

PostList.js
import React from "react"

function PostList({ resource }) {
	const posts = resource.posts.read() // change this

	return (
		<ul>
			{posts.map((post, i) => (
				<li key={i}>{post.title}</li>
			))}
		</ul>
	)
}

Cool, so that's basically how you can fetch data with React suspense. If you don't want to implement this code by yourself, especially the resource and wrapPromise part, you can use this awesome library called SWR. It offers some additional features like loading state, error catching, and custom fetcher.

Rahman Fadhil

I'm a software engineer specialized in iOS and full-stack web development. I can help you to learn new skills and solve your coding problems in Codementor.

Codementor badge