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 :-
Effortless Data Fetching: Fetch data with minimal boilerplate code using hooks like
useQuery
anduseInfiniteQuery
Caching: Caches data to prevent unnecessary network requests and improving performance.
Real-Time Updates: Refetches data once the cached data out of stale time.
Pagination / Load more fetching style: Really simplifies Handling paginated/ Lode More or infinite scrolling data effortlessly. with help of
useInfiniteQuery
hook.Error Handling: Built-in mechanisms to handle loading states, errors, and retries with ease.
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
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. ThequeryKey
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…
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 fileconst { 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
orfalse
) that tells you if there are more pages of data to load.If
true
, you can callfetchNextPage
; iffalse
, you've reached the end of the results.React Query Sets
hasNextPage
:React Query uses the result of
getNextPageParam
.If
getNextPageParam
returns a value (likenextPage
),hasNextPage
is set totrue
.If
getNextPageParam
returnsundefined
,hasNextPage
is set tofalse
.
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