How To Easily Fetch Data With React Hooks

How To Easily Fetch Data With React Hooks

Using state in function components
Ferenc Almasi • 🔄 2021 November 11 • 📖 7 min read

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.

Looking to improve your skills? Check out our interactive course to master React from start to finish.
Master React

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;
App.js
Copied to clipboard!

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;
App.js
Copied to clipboard!

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.

Looking to improve your skills? Check out our interactive course to master React from start to finish.
Master React

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());
App.js
Copied to clipboard!

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 ( ... );
}
App.js
Copied to clipboard!

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;
App.js
Copied to clipboard!

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);
     });
- });
+ }, []);
App.diff
Copied to clipboard!

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());
    })();
}, []);
App.js
Copied to clipboard!

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.

Using async function in useEffect will cause error
You’ll also get a self-explanatory warning about returning an async function.

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>
    );
}
App.js Make sure you check if the post is not null, to fall back to a loading screen
Copied to clipboard!
Looking to improve your skills? Check out our interactive course to master React from start to finish.
Master React

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!

Did you find this page helpful?
📚 More Webtips
Frontend Course Dashboard
Master the Art of Frontend
  • check Unlimited access to hundred of tutorials
  • check Access to exclusive interactive lessons
  • check Remove ads to learn without distractions
Become a Pro

Recommended