Skip to Content

React Query (TanStack Query) — Client & Server Usage Guide

Overview

React Query (TanStack Query)  is a powerful library for managing, caching, and synchronizing server state in your React applications. It abstracts away the complexity of data fetching, state management, and synchronization, making it ideal for working with APIs and asynchronous data.


Client-Side Usage

To use React Query on the client side, you must wrap your components with the QueryClientProvider, making the query client available throughout your app’s component tree.

1. Setup the Query Client

Define the QueryClient and use the QueryClientProvider at the root of your application. The defaultOptions are crucial for defining global behavior.

import { QueryClient, QueryClientProvider } from '@tanstack/react-query' const queryClient = new QueryClient({ defaultOptions: { queries: { refetchOnWindowFocus: false, // disable refetch when user switches back to tab retry: false, // disable automatic retries on failure staleTime: 1000 * 60 * 5, // default data stays fresh for 5 minutes } } }) export function AppProvider({ children }: { children: React.ReactNode }) { return ( <QueryClientProvider client={queryClient}> {children} </QueryClientProvider> ) }

Note: The defaultOptions allow you to define default behaviors for all queries and mutations, ensuring consistency.

2. Using useQuery

The useQuery hook is the core tool for fetching, caching, and managing data retrieval.

import { useQuery } from '@tanstack/react-query' const { data, isFetching, error, refetch } = useQuery({ queryKey: ['users'], // unique cache key for this query queryFn: async () => { const res = await fetch('/api/users') if (!res.ok) throw new Error('Failed to fetch users') return res.json() }, select: (data) => data.users, // optional: transform data before returning })
Common Returned ValuesDescription
dataThe resolved data from the query function.
isFetchingBoolean indicating if the query is currently fetching (includes background refetches).
isLoadingBoolean indicating if the query is fetching for the first time.
errorAny thrown error from the query function.
refetch()Function to manually trigger a refetch of the query.

3. Using useMutation

useMutation is used for any side effects that change server state (POST, PUT, DELETE, etc.).

import { useMutation, useQueryClient } from '@tanstack/react-query' // Get the client instance to interact with the cache const queryClient = useQueryClient() const { mutate, isPending, error } = useMutation({ mutationFn: async (newUser) => { // API call to create a new user const res = await fetch('/api/users', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(newUser), }) if (!res.ok) throw new Error('Failed to add user') return res.json() }, onSuccess: () => { // Invalidate the cache to trigger a refetch of the 'users' query queryClient.invalidateQueries({ queryKey: ['users'] }) }, }) // To trigger the mutation: // mutate({ name: 'Tareq', email: 'tareq@example.com' })

4. Other Query Types

React Query provides specialized hooks for advanced use cases:

  • useInfiniteQuery: Manages and fetches data for paginated/infinite scrolling lists.
  • useQueries: Allows you to run multiple useQuery instances in parallel, simplifying state tracking.
  • useSuspenseQuery: Integrates tightly with React Suspense for cleaner data loading states.

Server-Side Usage (SSR/SSG)

On the server side (e.g., in Next.js App Router or similar frameworks), you can prefetch queries during Server-Side Rendering (SSR) to ensure the data is immediately available to the client upon page load, minimizing loading spinners and improving UX. This process involves hydration.

1. Setup the Query Client (Server Side)

In an SSR environment, you need a mechanism to create a new QueryClient for every request on the server, while using a single persistent client on the browser.

import { isServer, QueryClient, defaultShouldDehydrateQuery } from '@tanstack/react-query' function makeQueryClient() { return new QueryClient({ defaultOptions: { queries: { refetchOnWindowFocus: false, retry: false, staleTime: 60 * 1000 // 1 minute stale time }, dehydrate: { // Includes pending queries in dehydration for suspense compatibility shouldDehydrateQuery: query => defaultShouldDehydrateQuery(query) || query.state.status === 'pending', // Next.js handles error redaction; we can disable it here shouldRedactErrors: () => false } } }) } let browserQueryClient: QueryClient | undefined = undefined // This function ensures a new client on the server, and a singleton on the client export function getQueryClient() { if (isServer) { // Server: always make a new query client per request return makeQueryClient() } else { // Browser: create a singleton client if one doesn't exist if (!browserQueryClient) browserQueryClient = makeQueryClient() return browserQueryClient } }

2. Prefetch Queries and Hydrate

Use prefetchQuery to fetch data on the server and HydrationBoundary to pass that data to the client.

And in the client component use useSuspenseQuery to prevent the query from refetching (userSuspenseQuery will take the result cashed through HydrationBoundary)

import { dehydrate, HydrationBoundary } from '@tanstack/react-query' import { getQueryClient } from './getQueryClient' // This component runs on the server export default async function Page() { const queryClient = getQueryClient() // 1. Prefetch the data on the server await queryClient.prefetchQuery({ queryKey: ['users'], queryFn: async () => { const res = await fetch('https://api.example.com/users') return res.json() }, }) // 2. Dehydrate the query client state const dehydratedState = dehydrate(queryClient) return ( // 3. Hydrate the client with the server-fetched data <HydrationBoundary state={dehydratedState}> <ClientComponent /> {/* ClientComponent can now use useSuspenseQuery(['users']) */} </HydrationBoundary> ) }

The HydrationBoundary makes the server-fetched data available to your client-side React Query hooks without triggering an extra network request on the client.


For the most up-to-date and detailed information, please refer to the official documentation:


Summary of Query Hooks

Hook / MethodPurposeTypical Use
useQueryFetch & cache data.Get a list of items or a single detail record from an API.
useMutationModify data on the server.POST, PUT, or DELETE requests.
useInfiniteQueryFetch paginated/chunked data.Implementing infinite scroll interfaces.
useQueriesRun multiple queries concurrently.Parallel fetching of independent data sets.
prefetchQueryPreload data on the server or client.Server-side Rendering (SSR) data preloading.