DEV Community

Riku Rouvila
Riku Rouvila

Posted on

Clean and reusable data fetching in React components

Move as much of the request logic to custom hooks as you can. Use SWR 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.

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;
Enter fullscreen mode Exit fullscreen mode

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:

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>
  );
};
Enter fullscreen mode Exit fullscreen mode

This how your components should look like โ˜๏ธ

How does useApiRequest hook work?

You can achieve this with many libraries such as SWR. 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:

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 };
};
Enter fullscreen mode Exit fullscreen mode

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

const { data, error, isLoaded } = useApiRequest(
  "https://jsonplaceholder.typicode.com/todos"
);
Enter fullscreen mode Exit fullscreen mode

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!

Top comments (2)

Collapse
 
mayankdutta profile image
Mayank Dutta

Hii, thanks for the info.
I have one doubt, if i were to define "useApiRequest" in another file and "Example" in other file. How will i pass data then, b/w those two files, data like {data, error, isLoaded} that i will be getting from useApiRequest.

Collapse
 
rjitsu profile image
Rishav Jadon

You can use Context API for fetching state between components. Consume the context with useContext hook which is natively available on React