This comparison table strives to be as accurate and as unbiased as possible. If you use any of these libraries and feel the information could be improved, feel free to suggest changes.
Feature Legend
- β
First-class, built-in, and ready to use with no added configuration or code
- π‘ Supported, but as an unofficial 3rd party or community library/contribution
- πΆ Supported and documented, but requires extra user-code to implement
- π Not officially supported or documented
Quick Comparison
| Feature | TanStack Query | SWR | Apollo Client | RTK Query | React Router |
|---|
| Platform Requirements | React | React | React, GraphQL | Redux | React |
| Supported Query Syntax | Promise, REST, GraphQL | Promise, REST, GraphQL | GraphQL, Any | Promise, REST, GraphQL | Promise, REST, GraphQL |
| Caching Strategy | Hierarchical Key β Value | Unique Key β Value | Normalized Schema | Unique Key β Value | Nested Route β value |
| Cache Change Detection | Deep Compare (Stable) | Deep Compare (Stable) | Deep Compare (Unstable) | Referential Equality | Route Change |
| Data Memoization | Full Structural Sharing | Identity (===) | Normalized Identity | Identity (===) | Identity (===) |
Core Features
Data Fetching & Caching
| Feature | TanStack Query | SWR | Apollo Client | RTK Query | React Router |
|---|
| Queries | β
| β
| β
| β
| β
|
| Mutations | β
| β
| β
| β
| β
|
| Cache Persistence | β
| β
| β
| β
| π Active Routes Only |
| Devtools | β
| β
| β
| β
| π |
| Auto Garbage Collection | β
| π | π | β
| N/A |
| Offline Caching | β
| π | β
| πΆ | π |
Query Types
| Feature | TanStack Query | SWR | Apollo Client | RTK Query | React Router |
|---|
| Parallel Queries | β
| β
| β
| β
| β
|
| Dependent Queries | β
| β
| β
| β
| β
|
| Paginated Queries | β
| β
| β
| β
| β
|
| Infinite Queries | β
| β
| β
| β
| π |
| Bi-directional Infinite | β
| πΆ | πΆ | β
| π |
| Infinite Query Refetching | β
| β
| π | β
| π |
Advanced Features
| Feature | TanStack Query | SWR | Apollo Client | RTK Query | React Router |
|---|
| Lagged Query Data | β
| β
| β
| β
| β
|
| Selectors | β
| π | β
| β
| N/A |
| Initial Data | β
| β
| β
| β
| β
|
| Scroll Recovery | β
| β
| β
| β
| β
|
| Cache Manipulation | β
| β
| β
| β
| π |
| Query Cancellation | β
| π | π | π | β
|
| Partial Query Matching | β
| πΆ | β
| β
| N/A |
Refetching & Invalidation
| Feature | TanStack Query | SWR | Apollo Client | RTK Query | React Router |
|---|
| Polling/Intervals | β
| β
| β
| β
| π |
| Window Focus Refetching | β
| β
| π | β
| π |
| Network Status Refetching | β
| β
| β
| β
| π |
| Stale While Revalidate | β
| β
| β
| β
| π |
| Stale Time Configuration | β
| π | π | β
| π |
| Automatic Refetch After Mutation | πΆ | πΆ | β
| β
| β
|
| Feature | TanStack Query | SWR | Apollo Client | RTK Query | React Router |
|---|
| Render Batching & Optimization | β
| β
| π | β
| β
|
| Outdated Query Dismissal | β
| β
| β
| β
| β
|
| Normalized Caching | π | π | β
| π | π |
Mutations
| Feature | TanStack Query | SWR | Apollo Client | RTK Query | React Router |
|---|
| Mutation Hooks | β
| β
| β
| β
| β
|
| Offline Mutation Support | β
| π | π‘ | π | π |
Server-Side & Hydration
| Feature | TanStack Query | SWR | Apollo Client | RTK Query | React Router |
|---|
| Prefetching APIs | β
| β
| β
| β
| β
|
| Cache Dehydration/Rehydration | β
| π | β
| β
| β
|
| React Suspense | β
| β
| β
| π | β
|
Configuration & Setup
| Feature | TanStack Query | SWR | Apollo Client | RTK Query | React Router |
|---|
| API Definition Location | Component, External | Component | GraphQL Schema | External Config | Route Tree |
| Pre-usage Configuration | β
| π | β
| β
| β
|
| Abstracted/Agnostic Core | β
| π | β
| β
| π |
Feature Explanations
Lagged Query Data
TanStack Query provides a way to continue to see an existing queryβs data while the next query loads (similar to the UX that Suspense provides natively). This is extremely important when writing pagination UIs or infinite loading UIs where you donβt want to show a hard loading state whenever a new query is requested.
Other libraries without this feature will render a hard loading state for new queries (unless prefetched), while the new query loads.
Render Optimization
TanStack Query has excellent rendering performance. By default, it automatically tracks which fields are accessed and only re-renders if one of them changes. You can configure this behavior:
- Set
notifyOnChangeProps to 'all' to re-render whenever the query updates
- Set
notifyOnChangeProps to ['data', 'error'] to only re-render when specific properties change
TanStack Query also batches updates together to ensure your application only re-renders once when multiple components use the same query.
Partial Query Matching
Because TanStack Query uses deterministic query key serialization, you can manipulate variable groups of queries without knowing each individual query key:
// Refetch all queries starting with 'todos'
queryClient.refetchQueries({ queryKey: ['todos'] })
// Invalidate specific queries with variables
queryClient.invalidateQueries({ queryKey: ['todos', { status: 'done' }] })
You can also use filter functions to match queries based on custom conditions.
Pre-usage Query Configuration
Queries and mutations can be fully configured with defaults before use. For example:
queryClient.setQueryDefaults(['todos'], {
queryFn: fetchTodos,
staleTime: 60_000,
})
// Later, only the key is needed
const { data } = useQuery({ queryKey: ['todos'] })
SWR only supports a global default fetcher, not per-query configuration.
Automatic Refetch After Mutation
For truly automatic refetching to happen after a mutation, a schema is necessary (like GraphQL provides) along with heuristics that help the library identify individual entities and entity types.
TanStack Query requires manual invalidation but provides powerful tools to make this easy:
const mutation = useMutation({
mutationFn: updateTodo,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['todos'] })
},
})
Normalized Caching
TanStack Query, SWR, and RTK Query do not support automatic normalized caching, which stores entities in a flat architecture to avoid data duplication.
Apollo Client excels at normalized caching due to its GraphQL schema awareness.
Bundle Size Comparison
Bundle sizes matter for performance. Hereβs how the libraries compare:
- TanStack Query: ~13 KB (minified + gzipped)
- SWR: ~5 KB (minified + gzipped)
- Apollo Client: ~33 KB (minified + gzipped)
- RTK Query: Included with Redux Toolkit (~18 KB)
- React Router: ~25 KB (minified + gzipped)
Bundle sizes are approximate and may vary with versions. Check bundlephobia.com for current sizes.
Which Library Should You Choose?
Choose TanStack Query if:
- You need a powerful, flexible async state management solution
- You want excellent TypeScript support
- You need advanced features like infinite queries, prefetching, and optimistic updates
- You value great DevTools and documentation
Choose SWR if:
- You want a minimal, lightweight solution
- Your use case is simple data fetching
- You prefer Vercelβs ecosystem
Choose Apollo Client if:
- Youβre using GraphQL
- You need normalized caching
- You benefit from the GraphQL ecosystem
Choose RTK Query if:
- Youβre already using Redux
- You want Redux DevTools integration
- You prefer the Redux architecture
Choose React Router if:
- Your data fetching is tightly coupled to routing
- You want built-in loader patterns
- Youβre using Remix or similar frameworks
Each library has its strengths. TanStack Query provides the best balance of features, performance, and developer experience for most applications.