How To Easily Fetch Data With React Hooks
With the introduction to React Hooks from React v16.8, we can handle state in function components. This paves the way to leave behind classes and simplify our codebase, reduce bundle since, and overall, provide a better performance for users.
A common use-case of using hooks is fetching data from your server or from a third party API as soon as the component loads. We will take a look at how this can be done with the useState
hook in conjunction with useEffect
. Letβs dive in.
Setting Up React
To set things up, Iβve used create-react-app. If you already have it installed, run npx create-react-app react-hooks
to bootstrap a new React application. Inside your App.js
file, you can get rid of everything for now, but make sure you import useState
and useEffect
.
import React, { useState, useEffect } from 'react'
import './App.css'
function App() {
return (
<div className="App">
<header className="App-header">
<h1>π</h1>
</header>
</div>
);
}
export default App;
Using useState
Using useState
lets you declare a state variable. A variable, that is bound to change. It provides the same functionality as this.state
does in a class. Add the following line to your function component:
import React, { useState, useEffect } from 'react'
import './App.css'
function App() {
const [post, updatePosts] = useState(null);
return (
<div className="App">
<header className="App-header">
<h1>π</h1>
</header>
</div>
);
}
export default App;
Here we used array destructuring. This is because useState
returns two values:
post
is the current state.updatePosts
is the function that will update the value of the first variable, which is our current state.
You can name them however you like. The only important thing here is that you have your state as the first element, and a function which updates it as the second element.
And what is the passed argument for? It tells React the initial state. In our case, this will be null
.
So we want to display a post. However, it is null
, so we need to update the variable somehow.
Fetching the Post
To fetch some mock data, Iβm going to be using JSON placeholder. Add the following function above your function component:
const getPost = () => fetch('https://jsonplaceholder.typicode.com/posts/1').then(response => response.json());
We can call this function to fetch a mock post. So you might think, all we need to do is call this function and call the update state function with the passed response.
function App() {
const [post, updatePosts] = useState(null);
getPost().then(response => updatePosts(response));
return ( ... );
}
While this will certainly update the state, it will cause an infinite loop. This happens because as soon as the state is updated, the function component gets re-rendered. It will run into the fetch on line:4 again and set the state causing a new re-render. And so on. To prevent this, we need to use another hook, called useEffect
.
Using useEffect
The useEffect
hook can be used to create side effects in a component. Things such as managing subscriptions, changing the DOM manually, or fetching data. You can think useEffect
as the componentDidMount
or componentDidUpdate
methods.
In our example, we will use it to only do a re-render once, when the data has arrived. Extend your component with the following:
import React, { useState, useEffect } from 'react'
import './App.css'
function App() {
const [post, updatePosts] = useState(null);
useEffect(() => {
getPost().then(posts => {
updatePosts(posts);
});
});
return (
<div className="App">
<header className="App-header">
<h1>π</h1>
</header>
</div>
);
}
export default App;
From line:7 till line:11, we use useEffect
to handle the state change. It expects a callback function that will run every time the component renders. Inside it, we fetch the posts and update the variable with updatePosts
.
The only problem with this is that it still causes an infinite loop. Letβs break down what will happen:
- The component renders and the
useEffect
is called on line:7 - It fetches the data and updates the state which causes a re-render
- The component renders again and
useEffect
is called once more. - The cycle continues.
To avoid infinite loops, we can pass a second optional parameter to useEffect
.
useEffect(() => {
getPost().then(posts => {
updatePosts(posts);
});
- });
+ }, []);
This tells useEffect
to compare the values of the state between re-renders. If they donβt match β meaning the state has already been changed β useEffect
wonβt run. This breaks the cycle and makes our component work as expected.
Using Async/Await
If youβre dealing with multiple requests, making use of async/await also makes sense to improve readability. When doing so, keep the following in mind.
// β This will throw an error
useEffect(async () => {
updatePosts(await getPost());
}, []);
// βοΈ This won't
useEffect(() => {
(async () => {
updatePosts(await getPost());
})();
}, []);
You need to place async
functions inside the body of useEffect
. This is because an effect must not return anything else besides a function. By making the function async
, it will return a Promise.
Putting together everything, the whole function will look like the following:
function App() {
const [post, updatePosts] = useState(null);
useEffect(() => {
(async () => {
updatePosts(await getPost());
})();
}, []);
if (!post) {
return <div>Loading...</div>
}
return (
<div className="App">
<header className="App-header">
<h2>{post.title}</h2>
</header>
<main>{post.body}</main>
</div>
);
}
Wrapping Up
React Hooks are a powerful addition to React. Apart from useState
and useEffect
, React also provides you a way to create your own custom hooks. If you would like to learn more about useState
, useEffect
, or hooks in general, make sure to check out their official documentation below:
Thank you for taking the time to read this article, happy coding!
Rocket Launch Your Career
Speed up your learning progress with our mentorship program. Join as a mentee to unlock the full potential of Webtips and get a personalized learning experience by experts to master the following frontend technologies: