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>
);
}