Tanstack Query

Tanstack Query

Tags
frontend
Published
TanStack Query is a powerful data-fetching and state management library that handles:
  • Server state caching
  • Background updates
  • Automatic revalidation
  • Error handling
  • Loading states
Key features:
  1. Automatic Caching: Query results are cached by their unique keys
  1. Background Refreshing: Updates data while displaying existing content
  1. Mutations: Manages data updates using useMutation
  1. Automatic Retries: Implements built-in retry logic for failed requests
  1. DevTools: Provides powerful debugging tools
 

How to use the tool?

 
Tanstack Query has 3 core concepts that cover most of what we need to know:
  • Queries
  • Mutations
  • Query Invalidation
 
But first we need to set it up.
 

Set up Steps:

 
  • Install with bash
    • npm i @tanstack/react-query
  • Similar to React contexts, wrap it around the App. Here is an example
    • import './App.css'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import Posts from './posts'; import CreatePost from './CreatePost'; const queryClient = new QueryClient(); function App() { return ( <div className="App"> <QueryClientProvider client={queryClient}> <CreatePost /> <Posts /> </QueryClientProvider> </div> ); } export default App;
 

Queries

TanStack Query's queries are persistent data subscriptions that automatically manage fetching, caching, synchronizing, and updating server state in your React applications.
Basic Example
// Basic query usage const { data, isLoading, error } = useQuery({ queryKey: ['uniqueKey'], queryFn: () => fetchData(), // Optional configurations staleTime: 5000, // Data considered fresh for 5s cacheTime: 1000 * 60 * 5, // Cache persists for 5min refetchOnWindowFocus: true, // Auto refetch when window regains focus retry: 3 // Retry failed requests 3 times });
Main benefits:
  1. Automatic Background Updates: Handles refetching stale data and window refocus
  1. Caching: Built-in caching with configurable stale times
  1. Loading & Error States: Provides status indicators like isLoading, isError
  1. Deduplication: Prevents duplicate requests for the same data
  1. Parallel & Dependent Queries: Can handle multiple queries and dependencies
  1. Garbage Collection: Automatically removes inactive/unused cache data
Important patterns:
  • Use meaningful queryKey arrays for identification and invalidation
  • Keep queryFn focused on data fetching
  • Configure stale/cache times based on data freshness needs
  • Use enabled option for dependent queries
  • Leverage select for data transformation
Queries are perfect for:
  • GET operations
  • Server state that needs caching
  • Data that requires background updates
  • Resources shared across components
 
Complete Use
import React from "react"; import { useQuery } from "@tanstack/react-query"; import axios from "axios"; const retrievePosts = async () => { const response = await axios.get( "https://jsonplaceholder.typicode.com/posts", ); return response.data; }; export default function Posts() { const {data:posts, isLoading, error } = useQuery({queryKey:["posts"], queryFn: retrievePosts}); if (isLoading) { return <div>Loading...</div>; } if (error) { return <div>Error: {error.message}</div>; } return ( <div> <h1>Posts</h1> {posts?.map(post => ( <div key={post.id}> <h2>{post.title}</h2> <p>{post.body}</p> </div> ))} </div> ) }
 
The function retrievePosts is the function that does the API call, the useQuery hook automatically:
  • Manages loading/error states during the fetch
  • Caches the response data
  • Returns destructured states for easy rendering:
    • isLoading: Shows loading indicator
    • error: Handles error states
    • data (renamed to posts): Contains the cached API response
This abstraction eliminates the need for manual state management (useState, useEffect) while providing optimized data fetching and caching out of the box.
 
Here is what queryKey is used for:
The queryKey (in this case ["posts"]) serves several important purposes in TanStack Query:
  1. Cache Identification
// This key uniquely identifies the cached data const {data:posts} = useQuery({ queryKey: ["posts"], // Acts as cache identifier queryFn: retrievePosts });
  1. Cache Invalidation
// In your mutation (like in CreatePost.js) onSuccess: () => { // Uses the same key to invalidate cached data queryClient.invalidateQueries({queryKey: ['posts']}); }
  1. Query Deduplication
// If multiple components use the same key const {data:posts} = useQuery({queryKey: ["posts"], queryFn: retrievePosts}); // Another component using same key const {data:samePosts} = useQuery({queryKey: ["posts"], queryFn: retrievePosts}); // TanStack Query will only make one network request
Keys can also be more complex for parameterized queries:
// Example with dynamic key useQuery({ queryKey: ["posts", userId, status], // ["posts", 123, "draft"] queryFn: () => fetchUserPosts(userId, status) });
The key acts like a unique ID for your query, enabling TanStack Query to efficiently manage caching, updates, and data sharing across your application.
 

Mutations and Query Invalidation

Mutation
Mutations in TanStack Query are used for modifying server-side data (POST, PUT, DELETE operations). Here's how they work:
// Example from your CreatePost.js const mutation = useMutation({ mutationFn: (newPost) => axios.post("/api/posts", newPost), onSuccess: () => { // Invalidate and refetch queries after success queryClient.invalidateQueries({queryKey: ['posts']}); } }); // Usage mutation.mutate(newData); // Triggers the mutation
Key features:
  • Status tracking: isLoading, isError, isSuccess
  • Callbacks: onSuccess, onError, onSettled
  • Automatic error handling
  • Integration with queries through cache invalidation
  • Optimistic updates possible with onMutate
Unlike queries which are for reading data, mutations are for writing, updating, or deleting data, and they help manage the side effects of these operations in your application.
Query Invalidation
Query invalidation in TanStack Query is the process of marking cached data as stale and triggering a refetch. Here's what's happening in your code:
onSuccess: () => { queryClient.invalidateQueries({queryKey: ['posts']}); }
When this runs:
  1. Marks the cached posts data as stale
  1. Triggers a background refetch of the posts
  1. Components using the 'posts' query will get fresh data
  1. UI updates automatically with new data
It's like telling TanStack Query "this data is outdated, we need fresh data." Common use cases:
  • After creating/updating/deleting data
  • When data might have changed on the server
  • When you want to force a refresh
The beauty is that it handles all the refetching and UI updates automatically - you just need to tell it when the data might be stale.
 
Complete Use
import React, { useState } from "react"; import { useMutation, useQueryClient } from "@tanstack/react-query"; import axios from "axios"; const CreatePost = () => { const [title, setTitle] = useState(""); const [body, setBody] = useState(""); const queryClient = useQueryClient(); const postFunction = (newPost) => { axios.post("https://jsonplaceholder.typicode.com/posts", newPost); } const mutation = useMutation({ mutationFn: postFunction, onSuccess: () => { queryClient.invalidateQueries({queryKey: ['posts']} ); } }) const submitPost = () => { mutation.mutate(); } if (mutation.isLoading) { return <span>Submitting...</span>; } if (mutation.isError) { return <span>Error: {mutation.error.message}</span>; } if (mutation.isSuccess) { return <span>Post submitted!</span>; } return( <div> <h1>Create Post</h1> <input type="text" placeholder="Title" value={title} onChange={(e) => setTitle(e.target.value)} /> <input type="text" placeholder="Body" value={body} onChange={(e) => setBody(e.target.value)} /> <button onClick={submitPost}>Create Post</button> </div> ) } export default CreatePost;
 
What is Tanstack Query providing us here?
State Management
  • Tracks if we're submitting (isLoading)
  • Knows if submission succeeded (isSuccess)
  • Catches any errors (isError)
  • All these states are automatically managed for us
Cache Management
  • Knows when data needs refreshing
  • Handles invalidating old data
  • Triggers refetching of data when needed
  • Manages this across components that share the same data
Data Synchronization
  • When we create a new post, automatically tells other parts of the app
  • Coordinates updates between different components
  • Keeps server data and UI in sync
  • Handles the background refreshing of data
Error Handling
  • Catches API errors automatically
  • Provides error details
  • Manages error state and recovery
Instead of manually writing all this logic with useState and useEffect, TanStack Query handles these complex data management tasks automatically, making it much easier to manage server state in React applications.