Effortless Data Querying in ReactJS with Tanstack Query

ยท

11 min read

Effortless Data Querying in ReactJS with Tanstack Query

Introduction :

In this blog post we will learn about tanstack query and how it makes a developers job easier. we will learn how to query and update data with practical examples and also see how tanstack query makes our job easy.

What is Tanstack Query?:

Tanstack query (formerly React query) is a well known server state management library for react or any other frontend library. It makes fetching, caching, synchronizing and updating server state in your web applications very easy and not cumbersome.

It provides you with hooks in react to make your data fetching, data updating and cache much easier.

Before Tanstack Query :

In order to understand how tanstack query makes our job easier we need to know how server state was managed before tanstack query.So we are going to code a few lines in JS below.

Consider a situation where you want to fetch data from a server using an API and then you want to display that data in your web page. For our example we'll be using "{JSON} Placeholder" (Free fake API for testing and prototyping) to fetch some fake todos.

  1. Create React app :

    First create a react app, i will be using codesandbox.io for the demonstration and i recommend you the same for testing your knowledge.

    Here i created a react app where App.js has Todo.jsx as child component. We'll be writing our code in this component itself.
    Here is the link of sandbox (completed) : https://codesandbox.io/p/sandbox/dazzling-lumiere-297t3l

  2. Making our API call :

    Firstly, let's declare a state variable using the useState Hook. Subsequently, we'll initiate our API call within a useEffect Hook, ensuring the call occurs as soon as the component mounts on the DOM.

    We will employ the fetch method to make the API call; however, for this task, I strongly recommend using Axios. Axios provides a wealth of features, making it a more versatile and user-friendly choice compared to the native fetch method.

  3. Rendering fetched data :

    Now that we have all fetched todos stored in "Todos" we can map on it to display all the todos.

    you can see the css that i have used in styles.css file.

  4. Using useState for Loading and Error handling :

    When making an API call, you initiate an asynchronous request to another server. It's essential to be mindful of potential delays in data retrieval, necessitating the display of a loading indicator on the page. Additionally, there is a possibility of encountering errors during the API call, so it's crucial to implement error handling. To address these concerns, we utilize two states: one for managing the loading state and another for handling errors.

    And since we are using fetch to make API request we have to move our existing code into a new async function inside useEffect for better error handling.

    let us test our app now, let us deliberately make a mistake to see if our error state and loading is working or not.

    Here i made a mistake while converting the fetched todo to json . Instead of using response.json() i used response.json.

    Will our page show, let us see

    so our error handling is working as expected.

    Now you know how messy our code looks, just to make a single API call we have to write this much messed up code.And it would even be more difficult if you are working on a project that has many API calls.

    To make this task easy we use Tanstack query.

API handling with Tanstack Query :

Now that you know how API can be handled traditionally with just JS in React. There is a much more easier and optimal way to handle them using Tanstack Query

Why Should you use Tanstack query :

  1. Less code

  2. Caching data

  3. Automatic handling of error, loading, and fetching states

    ...and many more

Using Tanstack Query :

Okay, let us make a fun project now, we will use RESTCountries API in this project. Here how our project will look at the end.

  1. Create Vite + React app :

    First we will make a simple react app using Vite. Refer vite docs on how to create vite app. After your basic react app structure is ready now is the time to create a new component called <FlagCard/>. And test that everything is working correct or not.

  2. Installing Tanstack Query :

    You can install tanstack query from referring the docs but i'll give you a basic overview.

    Open the terminal in VSCode and run the following command :

     $ npm i @tanstack/react-query
     # or
     $ pnpm add @tanstack/react-query
     # or
     $ yarn add @tanstack/react-query
    
  3. Tanstack query Setup :

    In your main.tsx file wrap the <App/> component like the below shown image.

    By wrapping the component with QueryClientProvider , which makes sure that all child components embedded inside the <QueryClientProvider/> has access to the Tanstack query library.

  4. Make our API request :

    Now that all the setup is done we will use fetch from Javascript to make our api request and use Tanstack query to query the data.

    So make use of useEffect hook to call our API as you did above in the blog.Make sure to use iife (Immediately Invoked Function Expression) inside useEffect as we need to use await keyword. And there is no need of maintaining a state for Loading and Error because tanstack query takes care of that, Now isn't that exciting. After you finish make sure to test it.

  5. useQueryhook :

    Now we will use useQuery hook provided by Tanstack query to query the data from API endpoint. So what is this usequery hook, useQuery hook does a couple things to handle the fetching process. First, it sets up the state to track data, error, status, and retry count. Next, it uses the useEffect hook to perform the passed in 'queryFn', fetch data, and update the state accordingly. Okay i you did not understand all of this lets just code you'll definitely understand this.

    To understand what useQuery does we need to understand how caching work in tanstack query or react query

    Now let us understand this diagram

    1. User sends a request: The user interacts with the UI, triggering a data request.

    2. "A wild query appeared!" : Tanstack Query intercepts the request and checks its cache.

    3. "Is there a valid cache?" : Tanstack Query determines if the requested data already exists in its local cache.

      • If YES:

        • Tanstack Query retrieves the data from the cache and returns it to the UI, ensuring a fast and responsive user experience.
      • If NOT:

        • The "trigger the call" step occurs.
    4. Trigger the call: React Query initiates a data fetch request to the backend codebase.

    5. Backend sends data: The backend processes the request and sends the data back to Tanstack Query.

    6. "Is the request stale?" : Tanstack Query checks if the fetched data is considered stale based on user-defined parameters (e.g., staleTime).

      • If NOT:

        • The data is considered fresh and is used to update the UI.
      • If YES:

        • Another data fetch is made immediately to ensure the UI reflects the latest information.
    7. Update the UI: Tanstack Query updates the UI components with the latest data, providing a seamless user experience.

    8. User is happy! : The user benefits from a performant and responsive application due to Tanstack Query's caching and data management capabilities.

     const {data,isError,isPending,isSuccess} = useQuery({queryKey,queryFn,staleTime})

Now let me explain the code snippet,

  1. useQuery: This is a hook provided by the React Query library. It is used for managing queries in a React application. Queries are a way to fetch and manage data from various sources like APIs.

  2. { data, isError, isPending, isSuccess }: These are the variables destructured from the result of the useQuery hook. They provide information about the state of the query.

    • data: It holds the actual data fetched from the query. If the query is not successful or still pending, data might be undefined.

    • isError: This is a boolean indicating whether an error occurred during the query execution.

    • isPending: Another boolean that represents whether the query is currently in progress or not.

    • isSuccess: A boolean indicating whether the query has successfully completed.

  3. { queryKey, queryFn, staleTime }: These are the parameters passed to the useQuery hook:

    • queryKey: It is an array that serves as a unique identifier for the query. The queryKey helps React Query understand which query is being referred to. It might contain dynamic values relevant to the query.

    • queryFn: This is a function that performs the actual data fetching. It's responsible for making the API request or fetching data from other sources.

    • staleTime: This parameter determines how long the data is considered fresh before it becomes "stale." If a new request is made after this time, React Query will automatically fetch fresh data in the background.

  1. using useQuery() hook in the project :

    Now we can use useQuery( ) hook as below shown in the image. Now as tanstack query handles state of data we don't need state to store any data and also we don't need useEffect because Tanstack query's useQuery hook automatically triggers the query function when necessary, such as when the component mounts, the query key changes, or the data becomes stale based on the staleTime configuration.

    Refer the below code and IndividualCountryProps is the type defined for API data that we receive. If you want to know the API structure then click here you can visualize the api response by clicking JSON Viewer button there.

     import React from "react";
     import { useQuery } from "@tanstack/react-query";
    
     // Define the type for each individual country's properties
     type IndividualCountryProps = {
       name: { common: string };
       continents: string[];
       flags: { png: string };
     };
    
     const FlagCard = () => {
       // Define a function to fetch country details asynchronously
       const fetchCountryDetails = async (): Promise<IndividualCountryProps[] | null> => {
         try {
           // Make an API call to retrieve country details
           const response = await fetch("https://restcountries.com/v3.1/all");
    
           // Check if the response is successful; otherwise, throw an error
           if (!response.ok) {
             throw new Error("Failed to fetch data");
           }
    
           // Parse the JSON response
           const data = await response.json();
           return data;
         } catch (error) {
           // Handle API errors and log them
           console.log("API error", error);
           return null;
         }
       };
    
       // Use the useQuery hook to manage and cache the asynchronous data fetching
       const {
         data: countryArray, // Retrieved data
         isPending,          // Loading state
         isSuccess,          // Successful data retrieval state
         isError,            // Error state
       } = useQuery({
         queryKey: ["GET_COUNTRY_DETAILS"], // Unique query key
         queryFn: fetchCountryDetails,      // Asynchronous function to fetch data
         staleTime: 10000,                      // Time in milli seconds before considering data stale
       });
    
       return (
         <>
           {/* Display a heading for the flag card section */}
           <h1 className="text-center text-3xl">Country Flags</h1>
    
           {/* Display a grid of country flags */}
           <div className="grid grid-cols-2 gap-2 md:grid-cols-4 lg:grid-cols-6 py-3 px-2">
             {/* Map through the countryArray and render individual country components */}
             {countryArray?.map((individualCountries: IndividualCountryProps) => (
               <div
                 className="fles gap-1 px-2 py-2 bg-red-300 text-black"
                 key={individualCountries.name.common}
               >
                 {/* Display the country flag image */}
                 <img
                   src={individualCountries.flags.png}
                   alt={individualCountries.name.common}
                   className="w-full"
                 />
    
                 {/* Display the country name */}
                 <h3 className="font-semi text-lg text-center">
                   {individualCountries.name.common}
                 </h3>
    
                 {/* Display the first continent of the country */}
                 <p className="font-light text-sm text-center">
                   {individualCountries.continents[0]}
                 </p>
               </div>
             ))}
           </div>
         </>
       );
     };
    
     export default FlagCard;
    
  2. Error handling and Loading State :

    Now after all this you can easily implement error handling and show if

    loading image while the data is being fetched.

    below is the complete code of the same :

     import React, { useEffect, useState } from "react";
     import { useQuery } from "@tanstack/react-query";
    
     type IndividualCountryProps = {
       name: { common: string };
       continents: string[];
       flags: { png: string };
     };
    
     const FlagCard = () => {
       const fetchCountryDetails = async (): Promise<
         IndividualCountryProps[] | null
       > => {
         try {
           const response = await fetch("https://restcountries.com/v3.1/all");
           if (!response.ok) {
             throw new Error("Failed to fetch data");
           }
           const data = await response.json();
           return data;
         } catch (error) {
           console.log("API error", error);
           return null;
         }
       };
    
       const {
         data: countryArray,
         isPending,
         isError,
       } = useQuery({
         queryKey: ["GET_COUNTRY_DETAILS"],
         queryFn: fetchCountryDetails,
         staleTime: 10000,
       });
    
       if (isPending) {
         return (
           <div className=" w-screen h-screen flex justify-center items-center">
             <img src="/src/assets/loading.gif" alt="Loading..." />
           </div>
         );
       }
    
       if (isError) {
         return (
           <div className="  w-screen h-screen flex justify-center items-center">
             <h2>There was an error fetching data</h2>
           </div>
         );
       }
    
       return (
         <>
           <h1 className="text-center text-3xl">Country Flags</h1>
           <div className="grid grid-cols-2 gap-2 md:grid-cols-4 lg:grid-cols-6 py-3 px-2">
             {countryArray?.map((induvidualCountries: IndividualCountryProps) => (
               <div
                 className="fles gap-1 px-2 py-2 bg-red-300 text-black"
                 key={induvidualCountries.name.common}
               >
                 <img
                   src={induvidualCountries.flags.png}
                   alt={induvidualCountries.name.common}
                   className="w-full"
                 />
                 <h3 className="font-semi text-lg text-center">
                   {induvidualCountries.name.common}
                 </h3>
                 <p className="font-light text-sm text-center">
                   {induvidualCountries.continents[0]}
                 </p>
               </div>
             ))}
           </div>
         </>
       );
     };
    
     export default FlagCard;
    

    Result:

Conclusion :

Hey folks, let's wrap up this journey into the world of Tanstack Query! ๐Ÿš€ We've dived deep into understanding how Tanstack Query, formerly known as React Query, can make a developer's life a whole lot easier when dealing with server state in React applications.

Remember the pre-Tanstack Query era? Yeah, it involved some hefty coding just to fetch, cache, sync, and update data. But now, with Tanstack Query, things have taken a turn for the better. It's like a magic wand for developers, simplifying the entire data-handling process.

In the blog, we took a trip down memory lane, exploring how we used to make API calls in React before Tanstack Query came along. The struggle was real โ€“ managing loading states, handling errors, and rendering data were no walk in the park

And also there is no many things in tanstack query that i did not write in this post like useMutation , using QueryClient etc.. But still i think this is a good beginning.

Hey there! This is my second blog, and I'm still learning the ropes. If there's anything you found confusing or if you have suggestions for improvement, please feel free to reach out. I'm open to feedback, and your insights will help me grow as a developer. You can contact me here. Thanks for being a part of this learning journey together!

My Twitter Profile

ย