How to Fetch Data with React Suspense
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.
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.
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
.
// ...
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.
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.
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.
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.
// ...
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.
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.
Tags: React, Javascript, Suspense