Clean and reusable data fetching in React components
Tags: react, api
Published: 2020-07-24 16:47:26

Move as much of the request logic to custom hooks as you can. Use [SWR](https://swr.vercel.app/) or implement your own hook for requesting data.

---

## Feels as if you're copy and pasting?
Fetching data in React components requires a lot of moving parts – you need to create a side effect, then request the data, then set the response to state (with information whether the request succeeded or if it's still loading) and only then you can use the data in the rendering phase of your component.

This feels like a lot of code (and repetition) to do such a frequent task.


```js
import * as React from "react";
import { useState, useCallback, useEffect } from "react";
import axios from "axios";
import "./styles.css";

const Example = () => {
  const [error, setError] = useState(null);
  const [isLoaded, setIsLoaded] = useState(false);
  const [items, setItems] = useState([]);

  const fetchItems = useCallback(() => {
    axios
      .get("https://jsonplaceholder.typicode.com/todos")
      .then(response => {
        setIsLoaded(true);
        setItems(response.data);
      })
      .catch(error => {
        setError(error);
      });
  }, []);

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

  if (error !== null) {
    return <div>Error: {error.message}</div>;
  } else if (!isLoaded) {
    return <div>Loading...</div>;
  } else {
    return (
      <div>
        {items.map(item => (
          <div>{item.id}</div>
        ))}
      </div>
    );
  }
};

export default Example;
```

*That's quite a lot* 😓 

So how could it be made differently? A good idea would be to move the generic parts to a separate module/function from where all components could use them. I'd say the shared concerns here are:

- Is the data loaded / loading?
- Was the request successful?
- How will the data be fetched?

So what if we could move handling all of those concerns out of this component altogether? Then our code would look more like this:

```js
const Example = () => {
  const { data, error, isLoaded } = useApiRequest(
    "https://jsonplaceholder.typicode.com/todos"
  );

  if (error !== null) {
    return <div>Error: {error.message}</div>;
  }
  if (!isLoaded) {
    return <div>Loading...</div>;
  }
  return (
    <div>
      {data.map(item => (
        <div>{item.id}</div>
      ))}
    </div>
  );
};
```
_This how your components should look like_ ☝️

### How does useApiRequest hook work?

You can achieve this with many libraries such as [SWR](https://swr.vercel.app/). You can also implement the hook yourself, which might be a good idea, especially if you haven't implemented a custom hook before or are just familiarizing yourself with the use of hooks. Having an understanding of how libraries work internally will help you see them as less magical and more logical. 

Your own custom hook could look as simple as this:

```js
const useApiRequest = url => {
  const [data, setData] = useState([]);
  const [isLoaded, setIsLoaded] = useState(false);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchData = () => {
      axios
        .get(url)
        .then(response => {
          setIsLoaded(true);
          setData(response.data);
        })
        .catch(error => {
          setError(error);
        });
    };
    fetchData();
  }, [url]);

  return { error, isLoaded, data };
};
```

So basically almost all the code you used to have in your component, now extracted from there and brought to a new function. Now all you need to do in your component is

```js
const { data, error, isLoaded } = useApiRequest(
  "https://jsonplaceholder.typicode.com/todos"
);
```

A full code example with refactored data fetching can be found from here: https://codesandbox.io/s/long-frost-qziu4?file=/src/App.js

Enjoy!