Getting started with TanStack

Getting started with TanStack

Let start with - What is TanStack Query?

Tanstack also know as react query, is a modern library for managing server state in your react/Next js applications. It really shines for its simplification of data fetching , caching, synchronization, and updating, making it an ideal choice for data fetching among developers, Does not matter whether you working with Rest APIs or GraphQL.

Key highlights :-

  1. Effortless Data Fetching: Fetch data with minimal boilerplate code using hooks like useQuery and useInfiniteQuery

  2. Caching: Caches data to prevent unnecessary network requests and improving performance.

  3. Real-Time Updates: Refetches data once the cached data out of stale time.

  4. Pagination / Load more fetching style: Really simplifies Handling paginated/ Lode More or infinite scrolling data effortlessly. with help of useInfiniteQuery hook.

  5. Error Handling: Built-in mechanisms to handle loading states, errors, and retries with ease.

  6. Background Updates: Keeps your app responsive by refetching data in the background without blocking the UI.

Why I Chose TanStack for My Project

In my marketplace app, products were refetched and reshuffled every time the page loaded, causing frustration when users returned after viewing a product, only to find it missing.

TanStack Query solved this by caching product data, ensuring the list stayed consistent across navigations, improving the user experience and API efficiency.

Enough theory , Lets see it in action

  1. useQuery

Installation

To start using TanStack Query in your React project, you’ll need to install it using npm or yarn.

      npm install @tanstack/react-query

Query client setup (React): Initialize QueryClient and wrap you application with QueryClientProvider , to make the query client available throughout your app.


      import {
        QueryClient,
        QueryClientProvider,
        useQuery,
      } from '@tanstack/react-query'

      const queryClient = new QueryClient() // Creating new instance of queryclient

      export default function App() {
        return (
          <QueryClientProvider client={queryClient}> // passing it throught out app
            <Example />
          </QueryClientProvider>
        )
      }

      function Example() {
        const { isPending, error, data } = useQuery({
          queryKey: ['repoData'],
          queryFn: () =>
            fetch('https://api.github.com/repos/TanStack/query').then((res) =>
              res.json(),
            ),
        })

        if (isPending) return 'Loading...'

        if (error) return 'An error has occurred: ' + error.message

        return (
          <div>
            <h1>{data.name}</h1>
            <p>{data.description}</p>
            <strong>👀 {data.subscribers_count}</strong>{' '}
            <strong>✨ {data.stargazers_count}</strong>{' '}
            <strong>🍴 {data.forks_count}</strong>
          </div>
        )
      }
  • queryKey: It's a unique identifier for a query, used by React Query to identify a specific query in the cache. It helps React Query decide whether it needs to refetch the data or retrieve it from the cache. The queryKey is typically an array.

  • queryFn: It's a function that fetches the data. It is called when the query is triggered, such as on the first render or during a refetch.

  • data: Data being returned from the backend

Query client setup (NextJs): For next js you need to wrap your layout with the query client provider like following:

Create providers.tsx file

Create the new instance of QueryClient and pass it to the QueryClientProvider

"use client";

import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const Providers = ({ children }: { children: React.ReactNode }) => {
  const queryClient = new QueryClient();
  return (
    <>
      <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
    </>
  );
};

export default Providers;

In your layout.tsx :

Wrap the children with the provider you created earlier, so your entire app can use React Query.

import { Inter } from "next/font/google";
import Providers from "@/components/providers/";

const inter = Inter({ subsets: ["latin"] });

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body className={inter.className}>
        <Providers>
          {children}
        </Providers>
      </body>
    </html>
  );
}

further the process is same to fetch the data as it was in react…

  1. useInfiniteQuery

    For data fetching when implementing infinite scroll or a "Load More" feature to retrieve additional data from the API, you should use useInfiniteQuery. This hook provides many useful features for implementing load more or infinite scroll functionality.

    Main code :

    Make sure you have imported useInfiniteQuery at the top of your file

  2.    const { data, isLoading, isFetching, fetchNextPage, hasNextPage, error } =
         useInfiniteQuery({
           queryKey: ["products"],
           queryFn: async ({ pageParam = 0 }) => {
             const response = await fetch(
               `${process.env.NEXT_PUBLIC_BACKEND_URL}/customer/products?skip=${
                 pageParam * 10
               }&limit=10`
             );
             if (!response.ok) throw new Error("Failed to fetch products");
             const data = await response.json();
             return {
               products: data.products,
               nextPage: pageParam + 1,
               hasMore:
                 data.products.length === 10 && data.total_count > pageParam * 10,
             };
           },
    
           getNextPageParam: (lastPage) =>
             lastPage.hasMore ? lastPage.nextPage : undefined,
           staleTime: 1000 * 60 * 20,
           initialPageParam: 0,
         });
    
    • fetchNextPage:

      • This is a function you call when you want to load more data, typically used in infinite scroll or a "Load More" button.

      • When called, it fetches the next set of results from the API.

    • hasNextPage:

      • A boolean (true or false) that tells you if there are more pages of data to load.

      • If true, you can call fetchNextPage; if false, you've reached the end of the results.

      • React Query Sets hasNextPage:

        • React Query uses the result of getNextPageParam.

        • If getNextPageParam returns a value (like nextPage), hasNextPage is set to true.

        • If getNextPageParam returns undefined, hasNextPage is set to false.

    • getNextPageParam:

      • A function that figures out the parameter (like pageParam) for the next batch of data.

      • In this case, it checks hasMore in the last page and returns the next page number if more data is available.

    • staleTime:

      • Specifies how long the data stays "fresh" in the cache.

      • For 20 minutes (1000 * 60 * 20), the cached data will be used without refetching unless explicitly refreshed.

    • initialPageParam:

      • The starting page parameter.

      • Here, it's set to 0, meaning the API starts fetching from the first page (or the first set of products).

       {hasNextPage && (
              <div className="text-center mt-8">
                <button
                  disabled={isFetching}
                  onClick={() => fetchNextPage()}
                  className="border border-[#D4D4D4] text-[#0A0A0A] font-OpenSans font-semibold text-[12px] leading-[16px] sm:text-[14px] sm:leading-[18px] rounded-full py-[12px] px-[16px]  "
                >
                  {isFetching ? "Loading..." : "Load More"}
                </button>
              </div>
            )}

So, if hasNextPage is true, we display a "Load More" button. When clicked, it calls the fetchNextPage function provided by useInfiniteQuery to get the next batch of data.

Official docs if you want to learn more:

https://tanstack.com/query/latest/docs/framework/react/overview