React Query (TanStack Query)

Powerful data fetching and caching for React

🔄 What is React Query?

React Query (TanStack Query) is a powerful library for fetching, caching, and updating server data in React applications. It handles loading states, errors, and caching automatically, eliminating complex data fetching logic.


// Simple React Query example
import { useQuery } from '@tanstack/react-query';

const { data, isLoading } = useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodos
});
                                    

Key React Query Features

🔄

useQuery

Fetch and cache data automatically

const { data } = useQuery({
  queryKey: ['user'],
  queryFn: getUser
});
✏️

useMutation

Create, update, or delete data

const mutation = useMutation({
  mutationFn: createTodo
});
💾

Caching

Automatic caching and background updates

// Cached for 5 minutes
staleTime: 5 * 60 * 1000
🔁

Refetching

Auto-refetch on window focus or reconnect

refetchOnWindowFocus: true

🔹 Setting Up React Query

Install React Query and set up the QueryClient provider to enable data fetching and caching throughout your entire React application.

# Install React Query
npm install @tanstack/react-query
// App.js - Setup QueryClient
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

const queryClient = new QueryClient();

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <YourApp />
    </QueryClientProvider>
  );
}

🔹 Basic Data Fetching with useQuery

useQuery hook fetches data and provides loading, error, and success states automatically. It caches results and handles refetching intelligently without extra code.

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

// Fetch function
const fetchPosts = async () => {
  const res = await fetch('https://api.example.com/posts');
  return res.json();
};

function Posts() {
  const { data, isLoading, error } = useQuery({
    queryKey: ['posts'],
    queryFn: fetchPosts
  });

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

  return (
    <ul>
      {data.map(post => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

🔹 Query Keys

Query keys uniquely identify queries for caching. Use arrays to include parameters, making it easy to cache different variations of the same data request.

// Simple key
useQuery({ queryKey: ['todos'], queryFn: fetchTodos });

// Key with parameters
useQuery({ 
  queryKey: ['todo', todoId], 
  queryFn: () => fetchTodo(todoId) 
});

// Complex key with filters
useQuery({ 
  queryKey: ['todos', { status: 'active', page: 1 }],
  queryFn: () => fetchTodos({ status: 'active', page: 1 })
});

🔹 Mutations with useMutation

useMutation handles create, update, and delete operations. It provides callbacks for success and error handling, plus automatic cache invalidation to keep data fresh.

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

function AddTodo() {
  const queryClient = useQueryClient();

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

  return (
    <button onClick={() => mutation.mutate({ title: 'New Todo' })}>
      Add Todo
    </button>
  );
}

🔹 Loading and Error States

React Query provides detailed status information for every query. Use these states to create better user experiences with loading indicators and error messages.

function UserProfile() {
  const { data, isLoading, isError, error, isFetching } = useQuery({
    queryKey: ['user'],
    queryFn: fetchUser
  });

  if (isLoading) {
    return <div>Loading user...</div>;
  }

  if (isError) {
    return <div>Error: {error.message}</div>;
  }

  return (
    <div>
      {isFetching && <span>Updating...</span>}
      <h1>{data.name}</h1>
      <p>{data.email}</p>
    </div>
  );
}

🔹 Caching and Stale Time

Control how long data stays fresh and when it should be refetched. Stale time determines when data is considered outdated and needs updating.

const { data } = useQuery({
  queryKey: ['posts'],
  queryFn: fetchPosts,
  staleTime: 5 * 60 * 1000, // 5 minutes
  cacheTime: 10 * 60 * 1000, // 10 minutes
  refetchOnWindowFocus: true,
  refetchOnReconnect: true
});

// Global defaults
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 60 * 1000, // 1 minute
      cacheTime: 5 * 60 * 1000 // 5 minutes
    }
  }
});

🔹 Dependent Queries

Run queries that depend on data from other queries. Use the enabled option to control when a query should execute based on available data.

function UserPosts({ userId }) {
  // First query - get user
  const { data: user } = useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetchUser(userId)
  });

  // Second query - depends on user data
  const { data: posts } = useQuery({
    queryKey: ['posts', user?.id],
    queryFn: () => fetchUserPosts(user.id),
    enabled: !!user // Only run when user exists
  });

  return <div>{posts?.map(post => post.title)}</div>;
}

🔹 Pagination Example

Implement pagination easily with React Query by including page numbers in query keys. Each page is cached separately for instant navigation between pages.

import { useState } from 'react';

function PaginatedPosts() {
  const [page, setPage] = useState(1);

  const { data, isLoading } = useQuery({
    queryKey: ['posts', page],
    queryFn: () => fetchPosts(page),
    keepPreviousData: true // Keep old data while fetching
  });

  return (
    <div>
      {isLoading ? (
        <div>Loading...</div>
      ) : (
        <ul>
          {data.posts.map(post => (
            <li key={post.id}>{post.title}</li>
          ))}
        </ul>
      )}
      <button onClick={() => setPage(p => p - 1)} disabled={page === 1}>
        Previous
      </button>
      <span>Page {page}</span>
      <button onClick={() => setPage(p => p + 1)}>
        Next
      </button>
    </div>
  );
}

🧠 Test Your Knowledge

Which hook is used for data fetching in React Query?