TanStack Query logo

TanStack Query

Powerful asynchronous state management for TS/JS, replacing manual data fetching logic with declarative hooks.

npm install tanstack-query
47.6K586/weekv1.0.05.86 KBISC128 issues
Last updated: 2024-03-08
Star history chart for TanStack/query

TL;DR

A comprehensive server-state management library that handles fetching, caching, synchronizing, and updating asynchronous data.

Framework-agnostic with deep React integration, effectively eliminating the need for manual `useEffect` fetching patterns.

Why TanStack Query?

TanStack Query (formerly React Query) has fundamentally changed how React developers handle asynchronous data. It separates server state (persisted remotely, asynchronous) from client state (UI themes, form inputs), solving the complexity of caching, deduping, and background updates that traditionally required complex useEffect boilerplate or global state managers like Redux.

  • Automatic Caching & Invalidation: Intelligent caching strategies that keep data fresh without manual intervention, using stale-while-revalidate patterns.
  • Declarative API: Hooks like useQuery and useMutation drastically reduce code volume compared to imperative fetching logic.
  • Background Synchronization: Automatically refetches data when the window focuses or the network reconnects.
  • Request Deduping: Prevents multiple components from triggering identical network requests simultaneously.
  • Integrated DevTools: Offers a dedicated GUI to visualize cache states, inspect query keys, and debug data flows.

Code Snippet

In v5, TanStack Query standardized on a single object argument for better type safety and extensibility.

import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'

// Fetcher function
const fetchTodos = async () => {
  const res = await fetch('/api/todos')
  return res.json()
}

function TodoList() {
  const queryClient = useQueryClient()

  // Query: Fetching data
  const { isPending, error, data } = useQuery({
    queryKey: ['todos'],
    queryFn: fetchTodos,
    staleTime: 1000 * 60, // Data is fresh for 1 minute
  })

  // Mutation: Updating data
  const mutation = useMutation({
    mutationFn: (newTodo) => {
      return fetch('/api/todos', {
        method: 'POST',
        body: JSON.stringify(newTodo),
      })
    },
    onSuccess: () => {
      // Invalidate and refetch
      queryClient.invalidateQueries({ queryKey: ['todos'] })
    },
  })

  if (isPending) return 'Loading...'
  if (error) return 'An error has occurred: ' + error.message

  return (
    <div>
      <ul>
        {data.map((todo) => (
          <li key={todo.id}>{todo.title}</li>
        ))}
      </ul>
      <button
        onClick={() => {
          mutation.mutate({ id: Date.now(), title: 'New Todo' })
        }}
      >
        Add Todo
      </button>
    </div>
  )
}

The code above demonstrates a standard read/write pattern. The staleTime configuration prevents unnecessary network requests, while invalidateQueries ensures the UI reflects the server state immediately after a mutation.

Pros and Cons

No library is perfect; understanding the trade-offs is key to selecting the right tool.

Pros

  • Eliminates Boilerplate: Removes the need for loading, error, and data state tracking variables in useEffect.
  • Backend Agnostic: Works with any function that returns a Promise (REST, GraphQL, tRPC, WebSockets).
  • Robust Feature Set: Includes pagination, infinite scrolling, optimistic updates, and dependent queries out of the box.
  • Excellent DX: Strong TypeScript inference and the dedicated DevTools make debugging asynchronous flows significantly easier.

Cons

  • Bundle Size: Heavier than alternatives like SWR, which might be a consideration for extremely performance-sensitive micro-applications.
  • Learning Curve: Understanding queryKey arrays, cache invalidation strategies, and the difference between staleTime and gcTime requires study.
  • Abstraction: For very simple apps fetching a single endpoint, it introduces more complexity than a native fetch call.

Comparison with Other Data Fetching Libraries

The table below outlines the positioning differences between TanStack Query and other popular libraries to help you make an informed decision:

LibraryDesign PhilosophyBest ForPain Points
TanStack Query"The Missing Data Library"
Complete server-state manager with extensive configuration.
Enterprise Apps
Complex applications requiring granular cache control, optimistic UI, and offline support.
Configuration Overhead
Can be overwhelming with many configuration options for simple use cases.
SWR"Stale-While-Revalidate"
Lightweight, focused on speed and simplicity, created by Vercel.
Next.js / SaaS
Projects prioritizing bundle size and seamless integration with the Vercel ecosystem.
Feature Depth
Lacks some of the advanced mutation and cache manipulation features found in TanStack Query.
Apollo Client"GraphQL Native"
Deeply integrated, normalized caching specifically for GraphQL.
GraphQL Heavy
Applications exclusively using GraphQL that benefit from normalized data stores.
Complexity & Lock-in
Very heavy bundle; difficult to use effectively with REST APIs.
RTK Query"Redux Integrated"
Built on top of Redux Toolkit, leveraging reducers for caching.
Redux Users
Teams already deeply invested in the Redux ecosystem who want a unified state model.
Boilerplate
Requires Redux setup and understanding; less ergonomic if you don't use Redux.