web-development react queryswr tanstackreact data fetching

React Query vs SWR vs TanStack: Ultimate 2025 Guide

Compare React Query, SWR, and TanStack for data fetching in 2025. Expert analysis with code examples to help you choose the right solution for your React app.

📖 11 min read 📅 February 24, 2026 ✍ By PropTechUSA AI
11m
Read Time
2.1k
Words
20
Sections

The landscape of React data fetching libraries has evolved dramatically, with developers now having powerful options that go far beyond basic fetch() calls. As React applications become more complex—especially in PropTech where real-time property data, user interactions, and complex state management are crucial—choosing the right data fetching solution can make or break your application's performance and developer experience.

The Evolution of React Data Fetching Libraries

Data fetching in React has transformed from simple component-level API calls to sophisticated state management solutions. The emergence of libraries like React Query, SWR, and TanStack Query has revolutionized how we handle server state, caching, and synchronization.

Why Traditional Approaches Fall Short

Traditional data fetching approaches using useEffect and useState create several challenges:

In PropTech applications, where property listings, pricing data, and user preferences need constant synchronization, these limitations become critical bottlenecks.

The Modern Data Fetching Paradigm

Modern libraries treat server state as a first-class citizen, distinct from client state. They provide:

Understanding the Core Players

React Query (Legacy) vs TanStack Query

React Query was rebranded to TanStack Query in 2022, expanding beyond React to support Vue, Svelte, and other frameworks. While "React Query" often refers to TanStack Query's React adapter, understanding this distinction is crucial for 2025.

typescript
// TanStack Query v5 (latest)

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

const PropertyQuery = () => {

const { data, isLoading, error } = useQuery({

queryKey: ['properties', filters],

queryFn: () => fetchProperties(filters),

staleTime: 5 * 60 * 1000, // 5 minutes

})

return (

<div>

{isLoading && <PropertySkeleton />}

{error && <ErrorBoundary error={error} />}

{data && <PropertyList properties={data} />}

</div>

)

}

SWR: Stale-While-Revalidate Philosophy

SWR, developed by Vercel, implements the stale-while-revalidate HTTP caching strategy. It's lightweight, focused, and integrates seamlessly with Next.js applications.

typescript
import useSWR from 'swr'

const PropertyDetails = ({ propertyId }: { propertyId: string }) => {

const { data, error, mutate } = useSWR(

/api/properties/${propertyId},

fetcher,

{

revalidateOnFocus: true,

revalidateOnReconnect: true,

}

)

const updateProperty = async (updates: PropertyUpdates) => {

// Optimistic update

mutate({ ...data, ...updates }, false)

await updatePropertyAPI(propertyId, updates)

mutate() // Revalidate

}

return <PropertyCard data={data} onUpdate={updateProperty} />

}

Feature Comparison Matrix

| Feature | TanStack Query | SWR | React Query v3 |

|---------|----------------|-----|-----------------|

| Bundle Size | ~39kb | ~4.2kb | ~36kb |

| TypeScript Support | Excellent | Good | Good |

| DevTools | Advanced | Basic | Advanced |

| Infinite Queries | Built-in | Manual | Built-in |

| Optimistic Updates | Advanced | Good | Advanced |

| Offline Support | Excellent | Basic | Good |

| Background Refetching | Configurable | Automatic | Configurable |

| Request Deduplication | Yes | Yes | Yes |

Implementation Deep Dive

Setting Up Your Data Layer

When building PropTech applications, establishing a robust data layer is essential. Here's how each library approaches setup:

#### TanStack Query Setup

typescript
// queryClient.ts

import { QueryClient } from '@tanstack/react-query'

export const queryClient = new QueryClient({

defaultOptions: {

queries: {

staleTime: 1000 * 60 * 5, // 5 minutes

gcTime: 1000 * 60 * 30, // 30 minutes (formerly cacheTime)

retry: (failureCount, error) => {

if (error.status === 404) return false

return failureCount < 3

},

},

},

})

// App.tsx

import { QueryClientProvider } from '@tanstack/react-query'

import { ReactQueryDevtools } from '@tanstack/react-query-devtools'

function App() {

return (

<QueryClientProvider client={queryClient}>

<PropertyApp />

<ReactQueryDevtools initialIsOpen={false} />

</QueryClientProvider>

)

}

#### SWR Configuration

typescript
// _app.tsx (Next.js)

import { SWRConfig } from 'swr'

import axios from 'axios'

const fetcher = (url: string) => axios.get(url).then(res => res.data)

function MyApp({ Component, pageProps }) {

return (

<SWRConfig

value={{

fetcher,

revalidateOnFocus: false,

errorRetryCount: 3,

errorRetryInterval: 5000,

dedupingInterval: 2000,

}}

>

<Component {...pageProps} />

</SWRConfig>

)

}

Advanced Patterns and Real-World Examples

#### Infinite Queries for Property Listings

Propertytech applications often require infinite scrolling for large datasets:

typescript
// TanStack Query infinite query

import { useInfiniteQuery } from '@tanstack/react-query'

const useInfiniteProperties = (filters: PropertyFilters) => {

return useInfiniteQuery({

queryKey: ['properties', 'infinite', filters],

queryFn: ({ pageParam = 0 }) =>

fetchProperties({ ...filters, page: pageParam }),

getNextPageParam: (lastPage, pages) =>

lastPage.hasNext ? pages.length : undefined,

initialPageParam: 0,

})

}

const PropertyListContainer = () => {

const {

data,

fetchNextPage,

hasNextPage,

isFetchingNextPage

} = useInfiniteProperties(filters)

return (

<InfiniteScroll

dataLength={data?.pages.flatMap(p => p.properties).length ?? 0}

next={fetchNextPage}

hasMore={hasNextPage}

loader={<PropertySkeleton />}

>

{data?.pages.map(page =>

page.properties.map(property =>

<PropertyCard key={property.id} property={property} />

)

)}

</InfiniteScroll>

)

}

#### SWR Infinite Loading

typescript
import useSWRInfinite from 'swr/infinite'

const useInfinitePropertiesSWR = (filters: PropertyFilters) => {

const getKey = (pageIndex: number, previousPageData: any) => {

if (previousPageData && !previousPageData.hasNext) return null

return /api/properties?page=${pageIndex}&${new URLSearchParams(filters)}

}

return useSWRInfinite(getKey, fetcher)

}

Handling Complex State Synchronization

In PropTech applications, you often need to synchronize related data. Here's how each library handles dependent queries:

typescript
// TanStack Query dependent queries

const usePropertyWithDetails = (propertyId: string) => {

const propertyQuery = useQuery({

queryKey: ['property', propertyId],

queryFn: () => fetchProperty(propertyId),

})

const neighborhoodQuery = useQuery({

queryKey: ['neighborhood', propertyQuery.data?.neighborhoodId],

queryFn: () => fetchNeighborhood(propertyQuery.data.neighborhoodId),

enabled: !!propertyQuery.data?.neighborhoodId,

})

const marketDataQuery = useQuery({

queryKey: ['marketData', propertyQuery.data?.zipCode],

queryFn: () => fetchMarketData(propertyQuery.data.zipCode),

enabled: !!propertyQuery.data?.zipCode,

})

return {

property: propertyQuery.data,

neighborhood: neighborhoodQuery.data,

marketData: marketDataQuery.data,

isLoading: propertyQuery.isLoading,

error: propertyQuery.error || neighborhoodQuery.error || marketDataQuery.error,

}

}

💡
Pro TipWhen working with dependent queries, always use the enabled option to prevent unnecessary API calls and improve performance.

Best Practices and Performance Optimization

Choosing the Right Library for Your Use Case

The decision between these libraries depends on your specific requirements:

#### Choose TanStack Query When:

#### Choose SWR When:

Performance Optimization Strategies

#### Query Key Management

typescript
// Good: Consistent, predictable query keys

const propertyKeys = {

all: ['properties'] as const,

lists: () => [...propertyKeys.all, 'list'] as const,

list: (filters: PropertyFilters) => [...propertyKeys.lists(), { filters }] as const,

details: () => [...propertyKeys.all, 'detail'] as const,

detail: (id: string) => [...propertyKeys.details(), id] as const,

}

// Usage

const { data } = useQuery({

queryKey: propertyKeys.detail(propertyId),

queryFn: () => fetchProperty(propertyId),

})

#### Smart Caching Strategies

typescript
// Configure different stale times based on data volatility

const usePropertyData = (propertyId: string) => {

// Property details change infrequently

const propertyQuery = useQuery({

queryKey: ['property', propertyId],

queryFn: () => fetchProperty(propertyId),

staleTime: 10 * 60 * 1000, // 10 minutes

})

// Market data changes frequently

const marketQuery = useQuery({

queryKey: ['market', propertyQuery.data?.zipCode],

queryFn: () => fetchMarketData(propertyQuery.data.zipCode),

staleTime: 30 * 1000, // 30 seconds

enabled: !!propertyQuery.data?.zipCode,

})

return { property: propertyQuery.data, market: marketQuery.data }

}

Error Handling and User Experience

typescript
// Comprehensive error handling

const useResilientPropertyQuery = (propertyId: string) => {

return useQuery({

queryKey: ['property', propertyId],

queryFn: async () => {

try {

return await fetchProperty(propertyId)

} catch (error) {

if (error.status === 404) {

throw new Error('Property not found')

}

throw error

}

},

retry: (failureCount, error) => {

if (error.message === 'Property not found') return false

return failureCount < 3

},

retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, 30000),

})

}

⚠️
WarningAlways implement proper error boundaries and fallback UI components to handle network failures gracefully, especially in PropTech applications where users expect real-time data.

Testing Strategies

typescript
// Mock queries for testing

import { QueryClient } from '@tanstack/react-query'

import { renderWithQuery } from '../test-utils'

describe('PropertyDetails', () => {

it('renders property data correctly', async () => {

const queryClient = new QueryClient({

defaultOptions: {

queries: { retry: false },

mutations: { retry: false },

},

})

// Prefill cache

queryClient.setQueryData(['property', '123'], mockPropertyData)

const { findByText } = renderWithQuery(

<PropertyDetails propertyId="123" />,

queryClient

)

expect(await findByText('Modern Downtown Condo')).toBeInTheDocument()

})

})

The 2025 Verdict: Making Your Decision

As we look at the data fetching landscape in 2025, each library has carved out its niche. At PropTechUSA.ai, where we handle complex property data workflows, real-time market updates, and sophisticated user interactions, the choice often comes down to project scope and team expertise.

TanStack Query: The Comprehensive Solution

TanStack Query has emerged as the most feature-complete solution. Its v5 release brings improved TypeScript support, better performance, and framework-agnostic capabilities. The learning curve is steeper, but the payoff is substantial for complex applications.

Best for: Large PropTech platforms, applications with complex data relationships, teams that need advanced debugging and optimization tools.

SWR: The Elegant Minimalist

SWR continues to excel in its simplicity and effectiveness. Its small bundle size and excellent Next.js integration make it perfect for projects where simplicity and performance are paramount.

Best for: Content-heavy sites, Next.js applications, projects prioritizing bundle size, teams preferring minimal configuration.

Migration Considerations

If you're currently using React Query v3, migrating to TanStack Query v5 provides significant benefits but requires careful planning. SWR offers easier migration paths and can be introduced incrementally.

The future of React data fetching is bright, with all three libraries continuing active development. Your choice should align with your team's expertise, project requirements, and long-term maintenance goals. Whether you're building the next generation of property management tools or creating immersive real estate experiences, any of these libraries will serve you well when implemented thoughtfully.

Ready to implement robust data fetching in your PropTech application? Start with a proof of concept using your chosen library and gradually migrate your most critical data flows. The investment in proper data management will pay dividends in application performance, developer experience, and user satisfaction.

🚀 Ready to Build?

Let's discuss how we can help with your project.

Start Your Project →