Fetching Data and Updating State with Hooks
The useEffect hook allows you to handle side effects such as logging, and making asynchronous calls, while the useState hook lets you give state to your functional components.
Dec 19, 2019 • 8 Minute Read
Fetching Data and Updating State with Hooks
A previous guide covered how to fetch data from a REST API and how to re-render a React component with the results. The guide detailed how to do this with the componentDidMount() lifecycle method that every React class component provides. Many components, however, are not written as classes. Most likely because classes are not yet fully supported by modern browsers, and it would require the use of Babel or TypeScript to transpile into supported JavaScript. When a component is created without using a class, but instead with a normal function declaration, it is called a Functional Component. In React, a functional component does not have the same built-in lifecycle methods that classes do. They also don't have their own state, so you can't call this.setState(...). As a result, you may want to know how to fetch data and re-render the component.
Introducing Hooks
As of version 16.8, React functional components have actually been able to use their own state and their own lifecycle methods. This functionality is possible due to React's new Hooks.
Hooks are functions that are prefixed with the word use... and allow you to hook into the component's lifecycle features from functional components.
There are 10 available built-in hooks, but the two most common hooks are useState and useEffect.
useState
According to the docs, the useState hook is similar to the this.setState() call in class components, except that it doesn't merge the old and new states together. The useState hook returns the state value and a function for updating that state:
import React from 'react';
function BooksList () {
const [books, updateBooks] = React.useState([]);
}
The above example shows how to use the useState hook. It declares a state variable called books and initializes it with an empty array. The array destructuring syntax might look intimidating, but it allows you to name your state variable and the updater function. That updater function allows you to update the state like this:
function BooksList () {
const [books, updateBooks] = React.useState([]);
const handleClick = () => {
// update the books state property by adding a new book
updateBooks([...books, { name: 'A new Book', id: '...'}]);
}
return (
<ul>
books.map(book => (
<li key={book.id}>{book.name}</li>
));
</ul>
);
}
When the above handleClick method is called, the updateBooks updater function will be called with the new array of books as its only argument. The books property is then updated and the component will re-render with the new value of books.
useEffect
When you must perform side effects in a functional component, you can use the helpful useEffect hook.
The Effect Hook lets you perform side effects in function components
The useEffect hook takes two arguments, a function and a list of dependencies. If the function or the dependencies change, the function is called.
function BooksList () {
const [books, updateBooks] = React.useState([]);
React.useEffect(function effectFunction() {
if (books) {
updateBooks([...books, { name: 'A new Book', id: '...'}]);
}
}, []); // This empty array represents an empty list of dependencies
...
}
The above example introduces the useEffect hook with an empty dependency list. As a result, this hook will only run when the component initializes. It won't run any other time because it doesn't have any dependencies to watch.
Using an empty array provides almost the same functionality as the componentDidMount() lifecycle method from a class component.
The following example shows what happens if you do provide a value in the dependency array:
function BooksList () {
const [books, updateBooks] = React.useState([]);
const [counter, updateCounter] = React.useState(0);
React.useEffect(function effectFunction() {
if (books) {
updateBooks([...books, { name: 'A new Book', id: '...'}]);
}
}, [counter]);
const incrementCounter = () => {
updateCounter(counter + 1);
}
...
}
In this example, the effectFunction will be called when the component initializes, and also every time the counter variable changes.
When using the useEffect hook, it's common to compare it to all of the class component lifecycle methods. Instead, you should think of the useEffect hook in terms of how you want your state to look after certain variables change.
Some rules to keep in mind about the useEffect hook:
- You cannot call a hook within a conditional;
- Hooks must be called in the exact same order. Putting the useEffect inside of a conditional could break that cycle;
- The function you pass the hook cannot be an async function, because async functions implicitly return promises, and a useEffect function either returns nothing or a cleanup function.
Bringing it Together
A common use case for which you'll need the useEffect is fetching some data from a server and updating the state with its contents. You can combine using the useEffect hook and the useState hook to accomplish this behavior. Imagine you want to fetch a list of Harry Potter books from a REST API. After the fetch, you want to display an unordered list of book titles in your component. That can look like this:
import React from 'react';
function BooksList () {
const [books, updateBooks] = React.useState([]);
React.useEffect(function effectFunction() {
fetch('https://the-fake-harry-potter-api.com/books')
.then(response => response.json())
.then(({ data: books }) => {
updateBooks(books);
});
}, []);
return (
<ul>
books.map(book => (
<li key={book.id}>{book.name}</li>
));
</ul>
);
}
The above hook only runs when the component is initialized. If you're more likely to use async/await in your useEffect hook, it may look like this:
function BooksList () {
const [books, updateBooks] = React.useState([]);
React.useEffect(function effectFunction() {
async function fetchBooks() {
const response = await fetch('https://the-fake-harry-potter-api.com/books');
const json = await response.json();
updateBooks(json.data);
}
fetchBooks();
}, []);
return (
<ul>
books.map(book => (
<li key={book.id}>{book.name}</li>
));
</ul>
);
}
Conclusion
Introducing the Hooks API provides a lot of functionality in your functional components. The useEffect hook allows you to handle side effects such as logging, making asynchronous calls, or setting values on local storage. The useState hook lets you give state to your functional components, which wasn't possible before unless you used a class component. A later guide will cover more hook functionality, including how to create your own custom hooks.
Please view this guide if you would like to learn how to update state and re-render a component using a class component.
Learn More
Explore these React courses from Pluralsight to continue learning: