TanStack Query vs SWR (2026): Modern React Server State Management
TanStack Query vs SWR (2026): Modern React Server State Management
Client-side state management has evolved. Modern React development separates local UI state (useState/Zustand) from server state (asynchronous data fetching, caching, deduplication, and invalidation).
TanStack Query (formerly React Query) and SWR (Stale-While-Revalidate by Vercel) are the two industry standards for managing server state in React.
This guide provides an objective, code-backed analysis of both libraries to help engineering teams choose the right abstraction for their stack.
Technical Comparison Matrix
| Feature / Metric | TanStack Query (v5) | SWR (v2) |
|---|---|---|
| Primary Creator / Maintainer | Tanner Linsley & TanStack Team | Vercel Engineering |
| Core Philosophy | Enterprise asynchronous state engine | Lightweight Stale-While-Revalidate hook |
| Gzip Bundle Size | ~13 KB | ~4.5 KB |
| Mutation API | Rich useMutation hook with onMutate, onError, onSettled |
useSWRMutation or imperative mutate() |
| Optimistic UI Updates | Built-in rollbacks via onMutate context |
optimisticData option in mutate() |
| DevTools | Dedicated TanStack Query DevTools extension | External community devtools |
| Infinite Queries | Native useInfiniteQuery with page parameters |
useSWRInfinite hook |
| Framework Agnostic | Core adapter for React, Vue, Solid, Svelte, Angular | React-focused |
1. Code Comparison: Data Fetching
TanStack Query (v5 Object Syntax)
TanStack Query uses an object-based configuration pattern with robust status states (isPending, isError, data):
import { useQuery } from '@tanstack/react-query';
async function fetchUserProfile(userId: string) {
const res = await fetch(`/api/users/${userId}`);
if (!res.ok) throw new Error('Failed to fetch user');
return res.json();
}
export function UserProfile({ userId }: { userId: string }) {
const { data, isPending, isError, error } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUserProfile(userId),
staleTime: 1000 * 60 * 5, // 5 minutes fresh
});
if (isPending) return <div>Loading user profile...</div>;
if (isError) return <div>Error: {error.message}</div>;
return <div>Welcome back, {data.name}</div>;
}
SWR (Hook Syntax)
SWR relies on string/array cache keys passed directly to a global or per-hook fetcher function:
import useSWR from 'swr';
const fetcher = (url: string) => fetch(url).then(res => res.json());
export function UserProfile({ userId }: { userId: string }) {
const { data, error, isLoading } = useSWR(
userId ? `/api/users/${userId}` : null,
fetcher,
{ dedupingInterval: 300000 }
);
if (isLoading) return <div>Loading user profile...</div>;
if (error) return <div>Error loading profile</div>;
return <div>Welcome back, {data.name}</div>;
}
2. Mutations & Optimistic UI Updates
Handling data mutations and updating the UI before the server responds is essential for modern high-performance web applications.
TanStack Query Optimistic Update Strategy
TanStack Query provides explicit lifecycle hooks (onMutate, onError, onSettled) for handling rollback on failure:
import { useMutation, useQueryClient } from '@tanstack/react-query';
export function useUpdateTodo() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: updateTodoApi,
onMutate: async (newTodo) => {
// Cancel outgoing refetches
await queryClient.cancelQueries({ queryKey: ['todos'] });
// Snapshot previous value
const previousTodos = queryClient.getQueryData(['todos']);
// Optimistically update cache
queryClient.setQueryData(['todos'], (old: any) => [...old, newTodo]);
return { previousTodos };
},
onError: (err, newTodo, context) => {
// Rollback to previous state if server fails
queryClient.setQueryData(['todos'], context?.previousTodos);
},
onSettled: () => {
// Always refetch after error or success to ensure sync
queryClient.invalidateQueries({ queryKey: ['todos'] });
},
});
}
SWR Optimistic Update Strategy
SWR simplifies optimistic mutations by accepting an optimisticData option inside its imperative mutate() function:
import useSWR, { mutate } from 'swr';
export function useUpdateTodo() {
const updateTodo = async (newTodo) => {
mutate(
'/api/todos',
updateTodoApi(newTodo),
{
optimisticData: (current) => [...current, newTodo],
rollbackOnError: true,
populateCache: true,
revalidate: true,
}
);
};
return { updateTodo };
}
3. DevTools & Debugging Experience
- TanStack Query DevTools: Includes an interactive floating inspector panel showing real-time query states (
fresh,fetching,stale,inactive,paused), active observers, and manual cache invalidation controls. - SWR: Lacks an official built-in DevTools panel, relying instead on browser console logging or community extensions.
4. Architectural Verdict & Recommendations
Choose TanStack Query if:
- You are building complex dashboard or enterprise applications with intensive background synchronization.
- You require robust DevTools to inspect cache states during development.
- You need framework-agnostic data layers that can be shared across React, Vue, or Solid micro-frontends.
- Your application heavily relies on optimistic updates with strict rollback requirements.
Choose SWR if:
- You are building on Next.js and prefer a lightweight (~4.5 KB) solution engineered by Vercel.
- Your application consists primarily of simple GET requests and basic form submissions.
- You want a zero-boilerplate setup with minimal mental model overhead.