All guides

What is TanStack Query?

TanStack Query is the gold standard for managing server state in React. Here's what it does and why it replaced useEffect for data fetching.

Josef Bender·

If you've ever written a useEffect to fetch data, you've probably also written a useState for the data, one for loading, one for error, some logic to avoid stale closures, and a cleanup function to cancel the request. And then done it again in the next component.

TanStack Query replaces all of that.

The Problem It Solves

Before TanStack Query, fetching data in React looked like this:

function UserProfile({ userId }) {
  const [user, setUser] = useState(null)
  const [isLoading, setIsLoading] = useState(true)
  const [error, setError] = useState(null)

  useEffect(() => {
    setIsLoading(true)
    fetch(`/api/users/${userId}`)
      .then((r) => r.json())
      .then(setUser)
      .catch(setError)
      .finally(() => setIsLoading(false))
  }, [userId])

  if (isLoading) return <Spinner />
  if (error) return <ErrorMessage error={error} />
  return <div>{user.name}</div>
}

This is a lot of boilerplate, and it doesn't common usecases like: background refetching, deduplication, or caching.

The TanStack Query Way

function UserProfile({ userId }) {
  const {
    data: user,
    isLoading,
    error,
  } = useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetch(`/api/users/${userId}`).then((r) => r.json()),
  })

  if (isLoading) return <Spinner />
  if (error) return <ErrorMessage error={error} />
  return <div>{user.name}</div>
}

Same result, a fraction of the code. But the real gains are what happens behind the scenes.

What TanStack Query Actually Does

Caching

Every query result is stored in a cache keyed by the queryKey. If another component mounts and requests the same key, it gets the cached data instantly from the cache, no second network request.

Background Refetching

When you revisit a page or refocus the browser tab, TanStack Query automatically refetches stale data in the background. The user sees the cached (potentially stale) data immediately, then gets a silent update when fresh data arrives.

Deduplication

If ten components mount simultaneously and all request the same query key, TanStack Query fires exactly one network request, then shares the result across all of them.

Automatic Retries

Failed requests are retried automatically (three times by default) with exponential backoff.

Loading and Error States

isLoading, isFetching, isError, isSuccess — all computed for you based on the query lifecycle.

Mutations

For writing data (POST, PUT, DELETE), TanStack Query offers the hook useMutation:

const { mutate, isPending } = useMutation({
  mutationFn: (newUser) =>
    fetch('/api/users', {
      method: 'POST',
      body: JSON.stringify(newUser),
    }).then((r) => r.json()),
  onSuccess: () => {
    queryClient.invalidateQueries({ queryKey: ['users'] })
  },
})

Calling queryClient.invalidateQueries after a successful mutation tells TanStack Query to refetch any cached data that matches so your UI stays fresh automatically.

Query Keys

Query keys are how TanStack Query identifies and manages cached data. They're arrays, and they work like a hierarchy:

['users'] // all users
[('users', { page: 1 })] // users on page 1
[('user', userId)] // a single user
[('user', userId, 'posts')] // posts for a specific user

You can invalidate an entire subtree at once: invalidateQueries({ queryKey: ['user', userId] }) would refetch both the user and their posts.

When to Use TanStack Query

Use TanStack Query when your component needs data from an external source: an API, a database, anything asynchronous.

It's the right tool for almost any server state problem in a React app.

For state that lives entirely in the browser (UI state, form state, selected items), you don't need it, reach for useState or a lightweight store instead.

Written by

Josef Bender

Software developer, IT consultant, and educator with 7+ years of React experience. Teaching thousands of developers online and in person.

Get in touch on Twitter or by email contact@josefbender.com.