Skip to main content

What does React Query (TanStack Query) do?

Short answer:

React Query (TanStack Query) is a "smart server-data manager" for React, which takes care of everything: loading, cache, refetch, errors, synchronization, optimistic updates, etc.

It makes working with an API reactive, fast, and safe, without manual useEffect and useState.

What React Query (TanStack Query) does

React Query is a "data client", which manages loading, caching, updating, and synchronizing data in React apps.

That is:

  • you describe how to fetch the data (queryFn) and what this data is (queryKey),
  • and React Query itself:
    • makes the request;
    • caches the result;
    • updates the UI when the data changes;
    • revalidates the cache;
    • manages "loading", "error", and "stale" states;
    • syncs everything on window focus, tab switching, etc.

In short: a "Server state manager"

React Query doesn't replace Redux / Zustand, because it's not for client state (UI state), but for server state - data fetched from an API.

Type of stateExampleWhere to store it
Client statemodal open/closed, selected tabuseState / Redux / Zustand
Server stateusers, posts, orders, productsReact Query

Example of basic usage

javascript
import { useQuery } from "@tanstack/react-query"; function UserProfile({ id }) { const { data, isLoading, error } = useQuery({ queryKey: ['user', id], queryFn: () => fetch(`/api/users/${id}`).then(res => res.json()), }); if (isLoading) return <p>Loading...</p>; if (error) return <p>Error loading user</p>; return <p>{data.name}</p>; }

React Query:

  • makes fetch() once;
  • caches the result under the key ['user', id];
  • if the component unmounts and appears again, the data is already in the cache, no new request needed;
  • if focus returns to the window, it automatically refreshes the data.

Main features

1. Data caching

React Query caches data in memory (QueryClient) with flexible settings:

javascript
<QueryClientProvider client={queryClient}> <App /> </QueryClientProvider>

Options:

javascript
useQuery({ staleTime: 5 * 60 * 1000, // considered fresh for 5 minutes cacheTime: 10 * 60 * 1000, // removed from memory after 10 min });

2. Stale-While-Revalidate

Shows cached (stale) data instantly, while making a request for fresh data in parallel and updating the UI afterward.

-> A UX with no loader "flicker".


3. Refetch on focus / reconnect

If a user minimized a tab and then came back:

  • React Query automatically refetches data (if it's "stale").
javascript
<QueryClientProvider client={new QueryClient({ defaultOptions: { queries: { refetchOnWindowFocus: true } } })}>

4. Deduplication

If two components call the same query:

javascript
useQuery({ queryKey: ['user', 1], queryFn: fetchUser }); useQuery({ queryKey: ['user', 1], queryFn: fetchUser });

React Query executes only one request, and both components receive the same data from the cache.


5. Mutations (POST / PUT / PATCH / DELETE)

For changing data there's useMutation():

javascript
import { useMutation, useQueryClient } from '@tanstack/react-query'; function UpdateUserButton({ id }) { const queryClient = useQueryClient(); const updateUser = useMutation({ mutationFn: (data) => fetch(`/api/users/${id}`, { method: 'PATCH', body: JSON.stringify(data), }), onSuccess: () => { // After a successful update, invalidate the cache queryClient.invalidateQueries(['user', id]); }, }); return ( <button onClick={() => updateUser.mutate({ name: 'Alex' })}> Update </button> ); }

6. Optimistic updates

Instantly updates the UI before the server responds, and rolls back on an error.

javascript
const mutation = useMutation({ mutationFn: likePost, onMutate: async (postId) => { await queryClient.cancelQueries(['post', postId]); const prev = queryClient.getQueryData(['post', postId]); queryClient.setQueryData(['post', postId], old => ({ ...old, likes: old.likes + 1 })); return { prev }; }, onError: (_, postId, ctx) => { queryClient.setQueryData(['post', postId], ctx.prev); }, onSettled: (postId) => { queryClient.invalidateQueries(['post', postId]); }, });

7. Pagination, Infinite scroll, Prefetch

Built-in support for pagination and loading pages:

javascript
useInfiniteQuery({ queryKey: ['posts'], queryFn: ({ pageParam = 1 }) => fetch(`/api/posts?page=${pageParam}`).then(r => r.json()), getNextPageParam: (lastPage) => lastPage.nextPage, });

8. Cache persistence

You can persist the cache across sessions:

javascript
import { persistQueryClient } from '@tanstack/react-query-persist-client'; persistQueryClient({ queryClient, persister: createSyncStoragePersister({ storage: window.localStorage }), });

-> data isn't lost on reload, requests aren't repeated.


What this looks like conceptually

React Query takes care of:

javascript
manual fetch() useEffect() useState() loading / error / success state caching and invalidation repeated requests on focus all automatically

Key concepts

TermWhat it is
QueryReading data (GET)
MutationChanging data (POST, PUT, DELETE)
Query KeyA unique identifier for a query in the cache
Query FunctionThe function that performs the request
Query ClientThe central cache manager
InvalidationUpdating the cache after changes
Stale TimeHow long the data is considered fresh
Cache TimeHow long before the data is removed from memory

Summary

CapabilityWhat React Query does
Automatic requestsmakes and repeats fetches
Cachingstores data by key
Data refreshrefetch on focus/change
State managementisLoading, isError, isFetching, data
Optimistic updatesinstant UI feedback
Cache invalidationrefreshes after a mutation
Deduplicationone request, many components
Cache persistenceacross tabs/sessions
Infinite Scroll / Paginationready-made hooks
No extra boilerplateno useEffect/useState for requests

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.