Data Fetching With React Hooks: Part 1

The author
Stephen Castle4 years ago

Let's learn to fetch data with ReactJS hooks from an API. Prior to hooks, it was most common to put our data fetching code in the componentDidMount() or componentDidUpdate() lifecycle method. When we use hooks, we are working with functional components and do not easily have access to the lifecycle methods. Using a simple example we can see how easy it is to use the useEffect and useState hooks to do data fetching.

If you haven't yet spent any time familiarizing yourself with React Hooks, I would recommend reading up on them a little bit here before continuing with this article. React Hooks at a Glance

What we want out of a data fetching solution?

For a robust data fetching solution, there are several issues we would like to consider and address if possible to make sure our data fetching is reliable and efficient.

Error Handling

We want our data fetching solution to handle error responses gracefully. We would also like the developer experience when writing error path code to be as pain-free as possible.

Cross Browser Support

It's a good idea to make sure whatever data fetching solution you choose works for all of the platforms you need to support. For example, the browser fetch API is not supported by IE11 without a polyfill.

Request Cancellation

If a request is no longer needed because say we navigated away from a route where we were still awaiting a response. We want to be able to cancel the request and avoid waiting when we no longer need to.

Response Caching

We should be able to have some method for avoiding duplicate requests and instead fulfill them from a local cache.

Support for React Suspense

React Suspense is a convenient way to render placeholder components while awaiting the results of some data fetching operation or other promises to be fulfilled. We want our data fetching solution to play nicely with this feature of React.

Server Side Rendering Support

When rendering a ReactJS app on the server React must execute your data-fetching calls in a node environment. We should choose a solution that works well both on the server and in the browser.

Small Bundle Impact

If we use a library for our data-fetching, it would be best if it doesn't add very much to our overall javascript payload.

An example of fetching data from the pokedex API with React Hooks

Let's take a look at a building a simple React app that has a list view, and a detail view. In our example we will add some hooks to grab data from the pokedex REST api for both the list and then for the item detail. In part one we will just get the basics set up and working, and then in part 2 move on to covering all the additional features we would like to have.

You can use this codesandbox.com template to follow along with the code below.

Using useEffect and useState hooks for data fetching

Now let's go through a simple example of data-fetching using hooks. We can use the awesome PokéAPI to build a simple application. We can do two things, request a list of Pokemon from the List.js component and then request detailed pokemon information on the Detail.js route and display the default sprite for the Pokemon.

Importing the dependencices we need.

// List.js
// highlight-start
import React, { useState, useEffect } from "react";
import axios from "axios";
// highlight-end
import { Link } from "react-router-dom";
function List() {
  const [data, setData] = useState([]);
  return (
    <div className="container">
      <ul className="list">
        {data.map((item) => (
          <li key={item.name} className="list-item">
            <Link to={item.name}>{item.name}</Link>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default List;

Creating our useEffect hook to fetch data

// List.js
import React, { useState, useEffect } from "react";
import axios from "axios";
import { Link } from "react-router-dom";
function List() {
  const [data, setData] = useState([]);
  // highlight-start
  useEffect(() => {
    // We declare an async function scoped inside of the useEffect callback param
    // this is because useEffect callbacks must be syncronous to avoid race conditions.
    async function fetchData() {
      const result = await axios("https://pokeapi.co/api/v2/pokemon");
      console.log(result);
      // Now that our results have returned we can use the useState setter to set our data to be the new results.
      setData(result.data.results);
    }
    fetchData();
    // Notice the second param to the function is [] this means
    // our useEffect hook should only run on initial component load. More on this later
  }, []);
  // highlight-end

  return (
    <div className="container">
      <ul className="list">
        {data.map((item) => (
          <li key={item.name} className="list-item">
            <Link to={item.name}>{item.name}</Link>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default List;

Data Fetching on the Detail Page

Fetching our pokemon details is almost the same as on the previous route, with two minor differences. We need to get the pokemon id from the route and use it in our API URI. Also, we need to make a change to the second parameter in our useEffect hook to update when the id changes.

// highlight-start
import React, { useState, useEffect } from "react";
import axios from "axios";
// highlight-end
import { Link } from "react-router-dom";
function Detail({
  match: {
    params: { id },
  },
}) {
  const [data, setData] = useState({});
  // highlight-start
  useEffect(() => {
    async function fetchData() {
      setIsLoading(true);
      const result = await axios(`https://pokeapi.co/api/v2/pokemon/${id}`);
      console.log(result);
      setData(result.data);
      setIsLoading(false);
    }
    fetchData();
    // Notice the difference here from the List.js example. This time since our request is dependent on the value of
    // id, we include it in this array. If it changes, our hook will execute its callback again.
  }, [id]);
  // highlight-end
  return (
    <div className="container">
      <div className="card">
        <h1>ID: {id}</h1>
        <div className="img-container">
          <img src={data.sprites.front_default} alt={data.name} />
        </div>
        <Link to="/">Return to List View</Link>
      </div>
    </div>
  );
}

export default Detail;

Try out the code sandbox below to see the finished example.

Handling performance and non-happy path concerns in part 2.

In part 2 of this 3 part series on data fetching using React hooks, we will see how to add error and loading spinner support, handle request cancellation, add suspense support, and implement response caching.