Introduction to Data Fetching with Hooks in React

September 9th, 2022 - by vlm55

React Hooks were introduced as a way to use state in React function components. They can be used for data fetching, let's see how, from scratch.


React and data

If you've ever worked on a Web Application before, you've surely bumped into data; we almost always need to work with data, regardless of the area of development. Whether it is fetching a table of sales records, or the content of a blog post, we almost always need to retrieve data in our applications.

If you've already familiar with Front-End Development, you already know about APIs such as Fetch (which is native to the browser), or Axios (which is a 3rd party library) which are used to help us retrieve data using an HTTP approach.

When working with React, the data fetching process is a bit tricky, as we have to make use of the useEffect hook to get around some particular issues and then rewrite our Axios query everywhere we need to fetch data again, and that takes some time away from the actual fun stuff.

In order to avoid further overhead of having to rewrite basically the same stuff in every component, and also make our code more modular, we should make use of React's Hooks.

They aren't helpful simply because they sound fancy and are a more modern way of writing our Applications, but they actually help us think and build our components with reusability and modularity in mind, which is great for the ever-increasing Applications we're used to working with.

So, without further ado, let's see how we can implement a Data Fetching Hook in React.

How the tutorial is built

As the use-case of this example, let's say that we wish to fetch a list of posts from an external API. In order to keep things short and concise, we'll handle the mapping and data fetching within the App.js file.

For the external API, rather than building a server from scratch, I'll be using JSONPlaceholder, as it is as straightforward as it could be, where we have access to data records such as posts, comments, albums, photos, todos & users; posts being what we are going to use for this project.

React and axios

For this project, the versions of the libraries I'll be using are:

  • axios: ^0.27.2
  • react: ^18.2.0

Build a react app

In order to create our React project we'll be using CRA. The way to do that is to go to the directory where you want to create your project and run any of the following commands, depending on the tool you like using:

  • npx:
$> npx create-react-app app-name
  • npm:
$> npm init react-app app-name
  • yarn:
$> yarn create react-app app-name

The versions of the packages that support those commands are:

  • npx: version 5.2 and higher of npm (For older version see this.)

  • npm: version 6 or higher

  • yarn: version 0.25 or higher

The versions I'm currently using are:

  • npx: 8.1.2

  • npm: 8.1.2

  • yarn: 1.22.11

Running any of the afore-mentioned commands should create a brand new React project with all the necessary files for us to be ready to build stuff.

Let's first start by going over an example where there isn't a hook to handle the data fetching process, so that we have a clearer picture of what the benefits of creating one are.

Fetching data without hooks

As mentioned earlier, we'll be using solely the App.js file under the root src/ directory to handle all of the data fetching and the mapping of the data.

import { useState, useEffect } from 'react';
import "./App.css";
import axios from  'axios';

function App()  {
    const [requestState, setRequestState] = useState({
        data: [],
        loading:  false,
        error:  null,
    });

    const fetchData = async () => {
        try {
            const { data: fetchedData } = await axios.get('https://jsonplaceholder.typicode.com/posts');

            setRequestState(prevState => ({
                ...prevState,
                data: fetchedData,
            }));
        } catch (err) {
            setRequestState(prevState => ({
                ...prevState,
                error: err,
            }));
        } finally {
            setRequestState(prevState => ({
                ...prevState,
                loading: false,
            }));
        }
    };

    useEffect(() => {
        fetchData();
    }, []);

    return (
        <div className="App">
            {requestState.loading ? (
                <p>Data is currently loading...</p>
            ) : requestState.error ? (
                <p>There was an issue loading the articles.</p>
            ) : (
                requestState?.data.map((article) => (
                    <div
                        className='article'
                        key={article.id}
                    >
                        <h3>{article.title}</h3>
                        <p>{article.body}</p>
                    </div>
                ))
            )}
        </div>
    );
};

export  default  App;

The example above is probably something you've seen before, as it's a quite standard approach to handling data fetching and the state of the request.

However, the issue is that, if we were to replicate the process elsewhere, we would be likely to need to copy some stuff over.

Even if we were to simply parameterize the data fetching function, there would still be the need to integrate with React's specifics, which, in our case, would be state handling and usage of a lifecycle method.

With hooks, however, we can implement that functionality within a particular hook which we would parameterize as well, the main benefit would be the integration with React's specifics; that's the selling point.

Fetching data using an useFetchData hook

Now that we've seen how we might implement data fetching without hooks, let's see how we can get an even better outcome by using them.

First, we would have to create a new /hooks/ directory under the root src/ directory, after which we would have to create our custom hook, which will go under src/hooks/useFetchData.js. The use word is a naming convention for hooks; you'll see that pretty much all hooks go by the useSomeHook name.

The hook would look something like this:

import axios from "axios";
import { useEffect, useState } from "react";

const useFetchData = (
    requestConfig, // Axios request config
) => {
    const localRequestConfig = requestConfig || {};
    const [state, setState] = useState({
        loading: true,
        data: null,
        error: null,
    });

    if (!localRequestConfig?.method) {
        localRequestConfig.method = 'GET';
    }

    useEffect(() => {
        if (localRequestConfig.url) {
            axios(localRequestConfig).then((res) => {
                setState(prevState => ({
                    ...prevState,
                    data: res.data,
                }));
            })
            .catch((err) => {
                setState(prevState => ({
                    ...prevState,
                    error:  err,
                }))
            })
            .finally(() => {
                setState(prevState => ({
                    ...prevState,
                    loading: false,
                }));
            });
        } else {
            setState(prevState => ({
                ...prevState,
                loading: false,
                error: new Error('No URL provided!'),
            }));
        }
    // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [requestConfig]);

    return state;
};

export default useFetchData;

You can notice it's all pretty similar to what we've previously had.

One difference being the use of .then, .catch & .finally instead of the try/catch/finally block, which is just a preference of mine, it is not required by any means.

Another difference you might've noticed is the use of the axiosConfig parameter, which is used to provide us with a more general-purpose hook. We wouldn't want a hook with any hard-coded values such as URLs or any other specific piece of configuration that we would have to change for other use cases.

Let's now see how we would implement this hook:

import "./App.css";
import useFetchData from  "./hooks/useFetchData";

function App()  {
    const requestConfig = {
        url: 'https://jsonplaceholder.typicode.com/posts',
    };

    const {
        data = [],
        loading,
        error,
    } = useFetchData(requestConfig);

    return (
        <div className="App">
      <h1>Articles</h1>
      <div className="articles-container">
        {loading ? (
          <p>Data is currently loading...</p>
        ) : error ? (
          <p>There was an issue loading the articles.</p>
        ) : (
          data.map((article) => (
            <div
              className='article'
              key={article.id}
            >
              <h3>{article.title}</h3>
              <p>{article.body}</p>
            </div>
          ))
        )}
      </div>
        </div>
    );
}

export  default  App;

We're now returning the state from the hook (which is reactive), and using that to help React manage its re-renders whenever needed, so you'll see that all the variables are working as expected:

A loading message will show up at first and then in case we have any errors we'll show an according message; otherwise, we'll render our data: that's the reactive flow in practice.

And, as you might notice, our App.js is looking pretty clean; we managed to reduce the file length by a lot just by creating a reusable hook component.

Just imagine the impact it would have on a larger-scale project that isn't using them.

Final Application
Final Working Application

Conclusion

In this article we managed to go over migrating a small project from having components fetching their own data to having a generic hook that takes as parameter an Axios request object that it uses to request data from an external URL.

We've also talked about and seen the benefits of such an approach, which, in a summary, are:

  • More modular and cleaner components
  • Looser coupling
  • Ability to reuse logic across components
  • Logic becomes easier to test

Hopefully, you've enjoyed reading, and learned something new that will help improve your projects by encouraging you to build more modular components, reuse logic across the board and make it easier to test the logic.

About

jsdevs&co was founded in September, 2022.

It allows Frontend and JavaScript developers to find the job they love, without agency or 3rd-party recruiters. Learn more in the about section.

The blog is opened :

  • Analytics for this blog is opened, so that you see the trafic for the blog.
  • Any developer that subscribed to jsdevs can have free training to SEO and technical writing.

We help developers to find the job they love ❤. See how →