In React applications, one of the most frequently executed tasks is to use the backend APIs and fetch data using fetch/axios/similar libraries and render it using the component. So, generally, the code has the following parts:

  1. React state to store the result of the API call as well as isLoading state to show the beautiful spinner

  2. Functional call to the API (most probably in useEffect)

  3. Statements in useEffect hook to update the state of the result

  4. Catch statement to show errors related to API (alert or other ways like toast, snackbar)

  5. Parameter list in useEffect to re-trigger the API call for filters/sort use case

A sample React code, which is covering the above points:

// apis.ts
// Sample function to get hotels
export const getHotels = (city: String) => {
  return fetch("baseURL" + `/hotels?city=${city}`);
};
// Hotels.tsx
"use client";
import React, { useEffect, useState } from "React";
import { getHotels } from "./apis";

const Hotels = () => {
  const [isLoading, setLoading] = useState(false);
  const [hotels, setHotels] = useState(null);

  useEffect(() => {
    setLoading(true);
    getHotels("Pune")
      .then((res) => {
        setHotels(res.data);
        setLoading(false);
      })
.catch((error) => {
        setLoading(false);
        // Error handling logic :(
      });
  }, []);

  if (isLoading) {
    // Spinner :)
  }

  return (
    <>
      <p>Hotels</p>
      {hotels?.map((hotel) => {
        return <>{hotel}</>;
      })}
    </>
  );
};

export default Hotels;

But this is probably not how you want to handle the data fetching as a React Developer, right?

While I was working on a recent frontend project, I found an interesting library called TanStack Query (Formerly React Query), which handles the above use case in a pretty developer-friendly way. And that is not the only advantage, we will see more use cases of the same going forward.

So here’s a simplified version of the above code using React Query:

// Hotels.tsx
"use client";
import React, { useEffect, useState } from "React";
import { getHotels } from "./apis";
import { useQuery } from "@tanstack/React-query";

const Hotels = () => {
  const { isError, isLoading, data } = useQuery({
    queryKey: ["hotels", "Pune"], // (1)
    queryFn: () => getHotels("Pune"), // (2)
    staleTime: 3000 // (3)
  });

  if (isLoading) {
    // Spinner :)
  }

  if(isError) {
    // Error handling logic :(
  }

  return (
    <>
      <p>Hotels</p>
      {data?.data?.map((hotel) => {
        return <>{hotel}</>;
      })}
    </>
  );
};

export default Hotels;

(1) → Query key parameters, which are used to generate keys used for caching purposes as well as unique identifiers for the query call. This key also helps for deduping across queries in your application. It would help if you were careful when providing precise key parameters.

(2) → Query function, which does handle data fetching from the server

(3) → Stale time for caching in milliseconds (this is optional, if not provided it will be used from the global QueryClient configuration)

The above example uses the useQuery hook to fetch the data, which under the hood uses QueryCache.

So, the useQuery hooks export some useful functions and properties such as isLoading (tells that the query is currently loading the data), data (used to store API response), isError (tells about the error / non 2XX statuses). It also provides other properties, which you can find here.

The above code saved you some lines of code and some heavy lifting done using states and hooks. This is just one simple example that React Query can handle. There are numerous use cases, which are difficult to achieve by using the normal way of thinking and doing data management in React applications.

Similarly, you can explore other useful hooks like useMutation, which is useful for data updates.

Here are some of the other interesting things that React Query can do apart from the above use case:

  1. Query Retries: Hooks like useQuery and useMutation allow you to provide the retry parameters, which can handle API failures, which is not natural to think about when you use your boilerplate way of fetching the data.

  2. Caching: This is the most crucial part of high data volume frontend applications and React Query does provide the application-wide and query-wide caching parameters and invalidation functions to work with.

  3. Background fetching: This feature allows the background fetching of out-of-date (stale) data even before you land to a particular page.

So, are you going to try this in your existing or new front-end project? If yes, then use the following resources to help you get started:

  1. Official Docs: https://tanstack.com/query/latest/docs/framework/React/overview

  2. https://tkdodo.eu/blog/inside-React-query (one of the major contributors to the library)