Fetching Data and Updating State in a React Class
Let's learn how to go about updating component state within a class component with data fetched from a server and clear up any confusion on the subject.
Oct 30, 2019 • 7 Minute Read
Introduction
Have you ever needed to render data in your React component that comes from a server? My guess is yes, yes you have. It's not always clear how this should be accomplished. You may be asking:
- Does this have to happen after a user interaction?
- Do I need Redux/Mobx for this?
- Is there a particular React library for this use case?
I want to explain how to go about updating component state within a class component with data fetched from a server and hopefully clear up any confusion on the topic.
Let's imagine we have an application that is all about Harry Potter. You can read all about the characters, their backstories, details about each book, etc. One of the components in this application lists every book in the Harry Potter series. The book details need to come from a Rest API, so let's see how to do that in a React class component.
State on Render
The problem we are trying to solve is:
When the component loads, I need it to create a request to my REST API to pull in some data.
The question then, is, "Where inside my component do I make that request?" The answer is in one of the lifecycle methods.
Class Component Lifecycle Methods
Although there are several lifecycle methods in a React component, the methods we will focus on for fetch requests are componentDidMount() and componentWillUnmount().
import React from 'react';
class ProductList extends React.Component {
componentDidMount() {
// make fetch request
}
componentWillUnmount() {
// make fetch request
}
render() {
return (
<ul>
<li />
</ul>
)
}
}
componentWillUnmount()
The componentWillUnmount() method is used when you need to perform an action when the component is unmounting. Common scenarios include unsubscribing from a realtime listener or tracking an unmount event. In the rest of the examples for this guide, we will not need the componentWillUnmount() method, so we'll work with the componentDidMount() method.
componentDidMount()
According to the React documentation:
componentDidMount() is invoked immediately after a component is mounted (inserted into the tree).
The componentDidMount method is a great place to request data from the server since even though setting the state will cause a second render, it happens before the browser updates the screen. The user will not see the intermediate state.
So how do I update the state?
What we are trying to do is fetch a list of Harry Potter books, and display them in a list.
I'd first want to set a books object on our initial state:
class BookList extends React.Component {
state = {
books: []
}
}
We'll render our books into an unordered list like this:
- Harry Potter and the Sorcerers Stone
- Harry Potter and the Chamber of Secrets
- Harry Potter and the Prisoner of Azkaban
- etc
class BookList extends React.Component {
state = {
books: []
}
render() {
return (
<ul>
{this.state.books.map((book) => (
<li key={book.id}>{book.name}</li>
))}
</ul>
)
}
}
To populate our state with books, we'll want to use the componentDidMount() lifecycle method.
..
componentDidMount() {
fetch('https://some-api.com/harry-potter')
.then((response) => response.json())
.then(booksList => {
this.setState({ books: booksList });
});
}
..
What Exactly Just Happened?
What's happening here is:
- The component will start to get inserted into the DOM.
- The initial render happens (with an empty array for books).
- Then componentDidMount() is called
- Once that request successfully finishes, setState() is called and the books property will be updated with all of the Harry Potter books.
Calling setState() triggers a new render and each book will now show in the unordered list.
Here's the complete example:
class BookList extends React.Component {
state = {
books: []
}
componentDidMount() {
fetch('https://some-api.com/harry-potter')
.then((response) => response.json())
.then(booksList => {
this.setState({ books: booksList });
});
}
render() {
return (
<ul>
{this.state.books.map((book) => (
<li key={book.id}>{book.name}</li>
))}
</ul>
)
}
}
Updating State on a User Event
What about when we want to make async requests after the initial load of the component? What if I want to fetch the books when a user has clicked a button? We can move our fetch call outside of the componentDidMount() method and call our API in a new method that we'll define as fetchBooks():
class BookList extends React.Component {
state = {
books: []
}
fetchBooks = () => {
fetch('https://some-api.com/harry-potter')
.then((response) => response.json())
.then(booksList => {
this.setState({ books: booksList });
});
}
render() {
return (
<>
<button onClick={this.fetchBooks}>Load Books</button>
{this.books.length > 0 && (
<ul>
{this.state.books.map((book) => (
<li key={book.id}>{book.name}</li>
))}
</ul>
)}
</>
)
}
}
What we did here is we added a button that, when clicked, makes the same fetch request that we previously had in the componentDidMount() method. I also added a condition around the rendering of the books list that says to only render the list if there are more than ZERO books.
Conclusion
Fetching and rendering data from a server is very common in a React application. The React.Component class gives us a lifecycle method that makes this easy to do, and I recommend using the componentDidMount() for making any async requests that happen when the component loads.
We'll talk about how to perform this same task in a Functional Component in another guide.