Tanstack Query Explained: A Complete Handbook

TanStack Query is useful for data fetching, caching, synchronizing, and updating server state in your React applications.

Why TanStack Query is Useful

  1. Data Fetching & Caching: Automatically handles caching of data so that you don't have to fetch the same data multiple times.

  2. Automatic Re-fetching: Data is automatically re-fetched when it becomes stale or if the app regains focus after going into the background.

  3. Mutation Management: Handles mutation (POST, PUT, DELETE requests) in an optimized way, providing options to revalidate data after mutations.

  4. Error Handling: Automatically retries fetching on errors, provides error states, and lets you handle retries.

  5. Stale Time & Refetch: You can configure how long data stays fresh and when it needs to be re-fetched.

Setup TanStack Query in a React App

1. Install Dependencies

You'll need to install @tanstack/react-query and axios for making API requests.

npm install @tanstack/react-query axios

2. Set up the QueryClientProvider

In your main index.js or App.js, wrap your app with the QueryClientProvider and pass the queryClient instance.

import React from "react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import ReactDOM from "react-dom";
import App from "./App";

// Create a client
const queryClient = new QueryClient();

ReactDOM.render(
  <QueryClientProvider client={queryClient}>
    <App />
  </QueryClientProvider>,
  document.getElementById("root")
);

Fetching Data with TanStack Query

Let's say you want to fetch a list of products for your e-commerce app. You can use the useQuery hook for fetching and caching data.

3. Fetching Products Example

import { useQuery } from "@tanstack/react-query";
import axios from "axios";

// API call function
const fetchProducts = async () => {
  const { data } = await axios.get("https://api.example.com/products");
  return data;
};

const ProductsList = () => {
  const { isLoading, error, data: products } = useQuery({
    queryKey: ["products"],
    queryFn: fetchProducts,
    staleTime: 5 * 60 * 1000, // Data stays fresh for 5 minutes
  });

  if (isLoading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <div>
      <h1>Products</h1>
      <ul>
        {products.map((product) => (
          <li key={product.id}>{product.name}</li>
        ))}
      </ul>
    </div>
  );
};

export default ProductsList;

Mutation (Adding to Cart) Example

For adding an item to the cart (a POST request), you can use the useMutation hook. After a successful mutation, you may want to re-fetch the cart data to keep it up-to-date.

4. Mutating Data (Add to Cart) Example

import { useMutation, useQueryClient } from "@tanstack/react-query";
import axios from "axios";

// Function to add a product to the cart
const addToCart = async (productId) => {
  const { data } = await axios.post("https://api.example.com/cart", { productId });
  return data;
};

const Product = ({ productId }) => {
  const queryClient = useQueryClient();

  const { mutate, isLoading, error } = useMutation({
    mutationFn: addToCart,
    onSuccess: () => {
      // Invalidate the cart query to refetch the cart data
      queryClient.invalidateQueries(["cart"]);
    },
  });

  if (isLoading) return <p>Adding to cart...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <button onClick={() => mutate(productId)}>Add to Cart</button>
  );
};

Refetching Data (Revalidating)

When you want to revalidate or refetch data, you can use the refetch function returned by the useQuery hook. Additionally, data is refetched when it becomes stale, or you can configure it to refetch when the window regains focus.

const { data: cartItems, refetch } = useQuery({
  queryKey: ["cart"],
  queryFn: fetchCartItems,
  staleTime: 1000 * 60, // Cart stays fresh for 1 minute
});

return (
  <div>
    <h1>Cart Items</h1>
    <button onClick={refetch}>Refetch Cart</button>
    {cartItems?.map((item) => (
      <p key={item.id}>{item.name}</p>
    ))}
  </div>
);

Additional Features

  1. Polling/Refetch Interval: You can poll data at specific intervals to keep it updated.

     useQuery({
       queryKey: ["products"],
       queryFn: fetchProducts,
       refetchInterval: 10000, // Refetch every 10 seconds
     });
    
  2. Invalidate Queries: After a successful mutation, invalidate specific queries to refetch them.

     queryClient.invalidateQueries(["cart"]);
    
  3. Optimistic Updates: You can optimistically update the UI before waiting for the server response.

     useMutation({
       mutationFn: addToCart,
       onMutate: async (newItem) => {
         await queryClient.cancelQueries(["cart"]);
         const previousCart = queryClient.getQueryData(["cart"]);
         queryClient.setQueryData(["cart"], (old) => [...old, newItem]);
         return { previousCart };
       },
       onError: (err, newItem, context) => {
         queryClient.setQueryData(["cart"], context.previousCart);
       },
       onSuccess: () => {
         queryClient.invalidateQueries(["cart"]);
       },
     });
    

Conclusion

TanStack Query simplifies the process of fetching, caching, mutating, and managing server-side data in a React app. In an e-commerce app, you can use it to:

  • Fetch and display products.

  • Manage cart actions (add to cart, update cart, etc.).

  • Handle mutations (adding/removing items from the cart) with automatic re-fetching.

  • Use features like caching, error handling, optimistic updates, and automatic refetching.

This approach helps you focus on the UI and business logic without worrying about data fetching boilerplate code.