How to Set Up TanStack Query in React

Set up TanStack Query in React with a QueryClient and provider. Install the package, wrap your app, and run a first useQuery with defaults.

6 min read

Set up TanStack Query in React by installing one package, creating a QueryClient, and wrapping your app in a provider. After that, any component can fetch and cache data with useQuery. TanStack Query v5 is the current major version.

Install the package

TanStack Query ships as a separate package for React. Install it from npm, then import everything you need from one module. It works with any React setup, including Vite and Next.js client components.

bashbash
npm install @tanstack/react-query

The package name is @tanstack/react-query. Version 5 uses an object-based API for queries and mutations, so you pass one options object instead of positional arguments. Older guides may show a v4 import or the old cacheTime option, so check the version when reading examples.

The package exposes useQuery, useMutation, and the QueryClient class. Nothing else needs configuring before you write code. The install also works with pnpm, Yarn, or Bun through the same package name.

Create a client and wrap the app

The QueryClient holds the cache and the default options. Think of it as the single source of truth for every fetched result. Create it once, then provide it to the component tree with the provider.

App.jsxApp.jsx
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
 
const queryClient = new QueryClient();
 
export default function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <Posts />
    </QueryClientProvider>
  );
}

Every component below the provider reads from the same cache. Two components that request the same key share one network request, which is the first big advantage over fetching in Effects.

Place the provider around the root of the app so every screen benefits. The client must stay stable, so create it outside the component function. Recreating it on every render would wipe the cache and restart pending requests.

Run your first query

A query is a call to useQuery with a key and a function that returns a promise of data. The hook subscribes the component to that key and returns states you render from instead of managing your own loading and error flags.

App.jsxApp.jsx
import { useQuery } from "@tanstack/react-query";
 
function Posts() {
  const { isPending, isError, data, error } = useQuery({
    queryKey: ["posts"],
    queryFn: () => fetch("/api/posts").then((res) => res.json()),
  });
  if (isPending) return <p>Loading posts...</p>;
  if (isError) return <p>Could not load posts: {error.message}</p>;
  return <ul>{data.map((post) => <li key={post.id}>{post.title}</li>)}</ul>;
}

isPending is true while the first request runs, isError is true after a failure, and data holds the result on success. isPending means the query has no data yet.

Later background refetches do not set isPending, because data is already on screen; they set isFetching instead, which is why the library separates the two flags. The query key names the cached result, which is what useQuery explained dives into next.

Set sensible defaults

TanStack Query refetches stale data on mount, window focus, and reconnect. That keeps data fresh but can surprise you with extra requests. Configure defaults on the client so every query inherits them.

App.jsxApp.jsx
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 60 * 1000,
    },
  },
});

Setting staleTime to one minute means fresh data is not refetched until it goes stale. Each query can still override this, but the global default keeps the behavior consistent.

Queries also retry failed requests three times by default, with a short exponential backoff before surfacing an error. Most apps set a global staleTime between 30 seconds and a few minutes, then tighten it per query for data that changes often.

The same setup pattern is the foundation for how to mutate data and invalidate queries with TanStack Query, and the plain fetch approach it replaces is covered in how to fetch API data in React.

What TanStack Query adds for free

Once the provider is in place, the library handles several things a plain fetch in an Effect does not. Caching means navigating back to a screen reads from memory instead of the network. Deduplication means two components with the same key share one request.

Background refetching keeps stale data updated without a loading screen, and retries recover from a failed request automatically. These behaviors are configurable, but they work out of the box with the setup you just finished. For a team, this removes a whole class of state and effect code that used to live in every data component.

Rune AI

Rune AI

Key Insights

  • Install @tanstack/react-query.
  • Create one QueryClient for the whole app.
  • Wrap the app in QueryClientProvider.
  • Call useQuery with a query key and a query function.
  • Set staleTime globally to tame aggressive refetching.
RunePowered by Rune AI

Frequently Asked Questions

Do I need a QueryClientProvider?

Yes. The provider shares one QueryClient cache across the component tree. Without it, useQuery has no client to read from and throws an error.

Where should I place the provider?

High in the tree, usually around the app's root component, so every screen can read from the same cache. It does not need to be inside every route.

Does TanStack Query work with Next.js?

Yes. It works in client components, and TanStack Query also provides server-side prefetching helpers for frameworks like Next.js.

Conclusion

Install @tanstack/react-query, create one QueryClient, and wrap the app in a QueryClientProvider. From there, useQuery handles fetching, caching, and loading and error states for you.