Data Fetching with React Query & Axios

Data Fetching with React Query & Axios

Implementing Hooks for Furry HTTP Requests in react-query

ยท

5 min read

There are many libraries that provide ways to make HTTP requests for the frontend but one remained the defacto leader for many years and that is axios. It's a neat library which allows for more structured and faster development but there's a library which recently surfaced called react-query. It's made for React and uses hooks and providers which are a more native approach in a React application and it's great.

1. Why not use only axios

Axios is a solid library don't get me wrong but react-query brings a caching mechanism under the hood and it's API is incredibly friendly for React users. This doesn't mean you can't use both at the same time if you want instead of using the browser based fetch API.

2. Axios Base Service

We will use the great Dog CEO API to show a random furry friend and a list of ๐Ÿถ. We want to make the request when the component mounts and destructure the response data (messsage) using a custom request function using axios.

import axios from "axios";

// we will use this amazing free dog api and declare our client
const client = (() => {
  return axios.create({
    baseURL: "https://dog.ceo/api"
  });
})();

// the request function which will destructure the response
// as it's shown in the Dog CEO API
const request = async function (options, store) {
  // success handler
  const onSuccess = function (response) {
    const {
      data: { message }
    } = response;
    return message;
  };

  // error handler
  const onError = function (error) {
    return Promise.reject(error.response);
  };

  // adding success and error handlers to client
  return client(options).then(onSuccess).catch(onError);
};

export default request;

Then we can define a DogService class which will use static methods to make the requests using the axios API.

import request from "./request";

export default class DogService {
  static getRandomDog() {
    return request({
      url: "/breeds/image/random",
      method: "GET"
    });
  }
  static getAllDogs() {
    return request({
      url: "/breeds/list/all",
      method: "GET"
    });
  }
}

We can use the requests in the App component and showcase our newly found furry friend as follows:

import { useEffect, useState } from "react";
import DogService from "./api/DogService";

export default function App() {
  // random image state
  const [image, setImage] = useState("");
  // dog breeds state
  const [dogs, setDogs] = useState([]);

  // useEffect to get random image on mount
  useEffect(() => {
     DogService.getRandomDog().then((response) => {
       setImage(response);
     });
  }, []);

  // useEffect to get all dog breeds on mount
  useEffect(() => {
     DogService.getAllDogs().then((response) => {
       setDogs(Object.keys(response));
     });
  }, []);

  return (
    <div className="App">
      <img src={image} alt="random dog" />
      {dogs.map((dog) => (
        <p>{dog}</p>
      ))}
    </div>
  );
}

What if we wanted to show a loading indicator until the getAllDogs() request is completed?

We would need to add an isLoading state variable and manually update it when request starts and completes.

  // rest ...

  const [isLoadingDogs, setIsLoadingDogs] = useState(false);
  // useEffect to get all dog breeds on mount
  useEffect(() => {
    setIsLoadingDogs(true);
    DogService.getAllDogs()
      .then((response) => {
        setDogs(Object.keys(response));
      })
      .finally(() => setIsLoadingDogs(false));
  }, []);

  return (
    <div className="App">
      <img src={image} alt="random dog" />
      {isLoadingDogs && <p>Loading...</p>}
      {!isLoadingDogs && dogs.map((dog) => <p>{dog}</p>)}
    </div>
  );
}

Also if we require to show an error message we have to define another error state variable and those pieces of state have to be handled for multiple requests in an application and it can get rather messy.

3. React Query Implementation

This works great but we can improve on it by caching the getAllDogs() request using react-query, which will also remove the need of useEffect to make request on mount and include error, isLoading values and many other useful stateful variables.

Update the index.js file at the root of our application to add the QueryClientProvider react-query uses to make the hooks globally available with some default settings. Also we'll add ReactQueryDevtools to debug the cached state with ease.

import { StrictMode } from "react";
import ReactDOM from "react-dom";
import { QueryClient, QueryClientProvider } from "react-query";
import { ReactQueryDevtools } from "react-query/devtools";

import App from "./App";

const rootElement = document.getElementById("root");

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      refetchOnWindowFocus: false,
      retry: false
    }
  }
});

ReactDOM.render(
  <StrictMode>
    <QueryClientProvider client={queryClient}>
      <ReactQueryDevtools initialIsOpen={false} />
      <App />
    </QueryClientProvider>
  </StrictMode>,
  rootElement
);

After that let's modify the App component with useQuery hook

import { useEffect, useState } from "react";
import { useQuery } from "react-query";
import DogService from "./api/DogService";

export default function App() {
  // random image state
  const { data: image } = useQuery(["image"], () => DogService.getRandomDog());
  const { data: dogsData, isLoading: isLoadingDogs } = useQuery(["dogs"], () =>
    DogService.getAllDogs()
  );

  const dogs = isLoadingDogs ? [] : Object.keys(dogsData);
  return (
    <div className="App">
      <img src={image} alt="random dog" />
      {isLoadingDogs && <p>Loading...</p>}
      {!isLoadingDogs && dogs.map((dog) => <p>{dog}</p>)}
    </div>
  );
}

This approach is much cleaner and our furry friend is back more optimized then ever.

image.png

Furthermore we can add options to customize and increase our performance even more.

const { data: dogsData, isLoading: isLoadingDogs } = useQuery(
    ["dogs"],  // query key very important
    () => DogService.getAllDogs(),
    {
      // time until stale data is garbage collected
      cacheTime: 60 * 1000,
      // time until data becomes stale
      staleTime: 30 * 1000
      // and many more
    }
);

You can find more details in the official documentation of react-query here or if you want to check this example check this codesandbox.

I hope you enjoyed this short guide and would love if you put a ๐Ÿ’œ on it!

ย