Overview
TamborraData leverages React Query 5 (@tanstack/query) as the core server state management solution, providing automatic caching, background synchronization, and seamless SSR integration with Next.js 16.
React Query eliminates the need for manual state management, loading states, and cache invalidation logic, reducing boilerplate by ~70% compared to traditional Redux patterns.
Why React Query?
Key Advantages
React Query automatically caches server responses with configurable TTL, eliminating redundant network requests and improving perceived performance. // Multiple components call this hook
// Only 1 HTTP request is made
useStatisticsQuery ( '2024' );
Background Synchronization
Data stays fresh through smart background refetching based on window focus, network reconnection, and time intervals.
First-class integration with Next.js Server Components enables instant content delivery and optimal SEO.
Identical requests made simultaneously are automatically deduplicated into a single network call.
Compared Alternatives
Feature React Query SWR Redux RTK Query Infinite cache ✅ ⚠️ Manual ✅ Conditional polling ✅ ✅ ✅ Next.js 16 SSR ✅ ⚠️ Limited ✅ Type safety ✅ ✅ ✅ Bundle size 13kb 4kb 45kb Request cancellation ✅ ❌ ✅ DevTools ✅ ❌ ✅
React Query provides the best balance between features and simplicity for data-heavy applications.
Global Configuration
The query client is configured in ReactQueryProvider.tsx with optimized defaults:
export const queryClient = new QueryClient ({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000 , // 5 minutes
gcTime: 30 * 60 * 1000 , // 30 minutes
retry: 1 , // 1 retry on failure
refetchOnWindowFocus: false , // No refetch on tab focus
refetchOnReconnect: false , // No refetch on reconnect
},
mutations: {
retry: 0 ,
},
},
});
Configuration Explained
staleTime
gcTime
retry
refetch policies
5 minutes - Historical statistics data doesn’t change frequently, so we can safely cache for extended periods.staleTime : 5 * 60 * 1000 // Data stays fresh for 5 minutes
30 minutes - Keep data in memory for fast navigation between pages without re-fetching.gcTime : 30 * 60 * 1000 // Cache persists for 30 minutes
1 retry - Give network requests a second chance on transient failures.retry : 1 // Retry once on error
Disabled - Manual control over when data refreshes, preventing unnecessary API calls.refetchOnWindowFocus : false ,
refetchOnReconnect : false ,
Caching Strategies
TamborraData implements three distinct caching patterns based on data characteristics:
1. Infinite Cache (Static Data)
For immutable historical data that never changes:
export function useStatisticsQuery ( year : string ) {
return useQuery ({
queryKey: queryKeys . statistics ( year ),
queryFn : () => fetchStatistics ( year ),
staleTime: Infinity , // Never marked as stale
gcTime: Infinity , // Never garbage collected
});
}
Used for annual statistics, expanded categories, and historical aggregates.
2. Conditional Polling (Dynamic Data)
For data that changes during update periods :
export function useStatisticsQuery ( year : string ) {
return useQuery ({
queryKey: queryKeys . statistics ( year ),
queryFn : () => fetchStatistics ( year ),
// Poll every 3 seconds when system is updating
refetchInterval : ( query ) =>
query . state . data ?. isUpdating ? 3000 : false ,
// Re-fetch on focus if updating
refetchOnWindowFocus : ( query ) =>
query . state . data ?. isUpdating === true ,
});
}
Behavior:
isUpdating = true → Poll every 3 seconds
isUpdating = false → No polling
User returns to tab → Resume polling if updating
3. Short TTL (Search Results)
For user searches with moderate volatility:
export function useParticipantsQuery ( params : SearchParams ) {
return useQuery ({
queryKey: queryKeys . participants ( params ),
queryFn : () => fetchParticipants ( params ),
staleTime: 2 * 60 * 1000 , // 2 minutes
gcTime: 10 * 60 * 1000 , // 10 minutes
});
}
Custom Hooks
All React Query usage is encapsulated in custom hooks for better organization and reusability.
useStatisticsQuery
export function useStatisticsQuery < T extends StatsResponse >( year : string ) {
return useQuery ({
queryKey: queryKeys . statistics ( year ),
queryFn : ({ signal }) => fetchStatistics < T >( year , signal ),
enabled: Boolean ( year ),
staleTime: Infinity ,
gcTime: Infinity ,
retry: 0 ,
refetchOnWindowFocus : ( query ) => query . state . data ?. isUpdating === true ,
refetchInterval : ( query ) => ( query . state . data ?. isUpdating ? 3000 : false ),
});
}
Features:
✅ AbortController support for request cancellation
✅ Conditional polling based on isUpdating flag
✅ Generic type support for different response shapes
✅ Infinite cache for historical data
useCategoryQuery
export function useCategoryQuery ( year : string , category : string ) {
return useQuery ({
queryKey: queryKeys . category ( year , category ),
queryFn : () => fetchCategory ( year , category ),
enabled: Boolean ( year && category ),
staleTime: Infinity ,
gcTime: Infinity ,
});
}
useYearsQuery
export function useYearsQuery () {
return useQuery ({
queryKey: queryKeys . years (),
queryFn: fetchYears ,
staleTime: Infinity ,
gcTime: Infinity ,
});
}
SSR and Prefetching
TamborraData uses Next.js 16 Server Components with React Query for optimal SSR performance.
Prefetching Strategy
// page.tsx (Server Component)
import { dehydrate , HydrationBoundary } from '@tanstack/react-query' ;
import { getQueryClient } from '@/lib/getQueryClient' ;
export default async function StatisticsPage ({ params } : Props ) {
const queryClient = getQueryClient ();
// Prefetch on server
await queryClient . prefetchQuery ({
queryKey: queryKeys . statistics ( params . year ),
queryFn : () => fetchStatistics ( params . year ),
});
return (
< HydrationBoundary state = { dehydrate ( queryClient )} >
< StatisticsContent year = {params. year } />
</ HydrationBoundary >
);
}
SSR Data Flow
Server Prefetch
Server component prefetches data and populates React Query cache. await queryClient . prefetchQuery ({
queryKey: [ 'statistics' , '2024' ],
queryFn : () => fetchStatistics ( '2024' ),
});
Dehydration
Cache is serialized and embedded in the HTML response. const dehydratedState = dehydrate ( queryClient );
HTML Delivery
Browser receives fully rendered HTML with embedded data.
Client Hydration
React Query rehydrates cache on client, avoiding duplicate requests. < HydrationBoundary state = { dehydratedState } >
< Component />
</ HydrationBoundary >
Instant Display
Component reads from cache immediately - no loading state! const { data } = useStatisticsQuery ( '2024' );
// data is immediately available
With SSR prefetching, users see content instantly and search engines get fully rendered HTML for optimal SEO.
Conditional Polling System
The polling system is TamborraData’s most sophisticated React Query feature.
Why Polling?
During January, the system generates new data automatically. The frontend detects this via the isUpdating flag and activates real-time polling.
Implementation
refetchInterval : ( query ) => {
const isUpdating = query . state . data ?. isUpdating ;
return isUpdating ? 3000 : false ;
};
refetchOnWindowFocus : ( query ) => {
// Only refetch if currently updating
return query . state . data ?. isUpdating === true ;
};
Behavior Matrix
System State Polling Active Refetch on Focus isUpdating = true✅ Every 3s ✅ Yes isUpdating = false❌ No ❌ No Network error ❌ Stops ❌ No
Polling consumes server resources. Always implement conditional logic to prevent unnecessary requests.
Query Keys Organization
Structured query keys enable precise cache invalidation:
// lib/queryKeys.ts
export const queryKeys = {
all: [ 'tamborradata' ] as const ,
statistics : ( year : string ) =>
[ ... queryKeys . all , 'statistics' , year ] as const ,
category : ( year : string , cat : string ) =>
[ ... queryKeys . statistics ( year ), cat ] as const ,
years : () =>
[ ... queryKeys . all , 'years' ] as const ,
companies : () =>
[ ... queryKeys . all , 'companies' ] as const ,
participants : ( params : SearchParams ) =>
[ ... queryKeys . all , 'participants' , params ] as const ,
};
Benefits
Selective Invalidation Invalidate specific queries without affecting others: // Invalidate all 2024 statistics
queryClient . invalidateQueries ({
queryKey: queryKeys . statistics ( '2024' )
});
Type Safety TypeScript ensures query keys are used correctly: const key = queryKeys . statistics ( '2024' );
// ['tamborradata', 'statistics', '2024']
Easy Debugging Hierarchical structure makes cache inspection simple in React Query DevTools.
Consistency Centralized keys prevent typos and ensure consistency across the codebase.
Request Deduplication
React Query automatically deduplicates identical requests:
// Multiple components call this hook simultaneously
< ComponentA /> ──┐
< ComponentB /> ──┤→ 1 single HTTP request
< ComponentC /> ──┘
useStatisticsQuery ( '2024' );
Request Cancellation
queryFn : ({ signal }) => fetchStatistics ( year , signal );
// In fetchStatistics
export async function fetchStatistics ( year : string , signal ?: AbortSignal ) {
const response = await fetch ( `/api/statistics?year= ${ year } ` , { signal });
return response . json ();
}
Benefit: If users navigate away quickly, in-flight requests are cancelled automatically.
Lazy Hydration
< HydrationBoundary state = { dehydrate ( queryClient )} >
{ /* Only rehydrates when component mounts */ }
< StatisticsContent />
</ HydrationBoundary >
Best Practices
Always wrap useQuery in custom hooks for better organization: // ✅ Good
export function useStatisticsQuery ( year : string ) {
return useQuery ({
queryKey: queryKeys . statistics ( year ),
queryFn : () => fetchStatistics ( year ),
});
}
// ❌ Bad
useQuery ({
queryKey: [ 'statistics' , year ],
queryFn : () => fetch ( '...' )
});
For immutable data, use infinite cache to eliminate unnecessary refetches: staleTime : Infinity ,
gcTime : Infinity ,
Implement Conditional Logic
Use dynamic options for smart caching: refetchInterval : ( query ) =>
query . state . data ?. needsPolling ? 3000 : false ,
Enable Request Cancellation
Always pass AbortSignal to fetch calls: queryFn : ({ signal }) => fetch ( url , { signal }),
Resources
React Query Docs Official TanStack Query documentation
SSR Guide Server-side rendering with React Query
Advanced Patterns Practical React Query patterns by TkDodo
Next.js Integration Advanced SSR patterns with Next.js