Caching Strategy with TanStack Query
Overview​
We use TanStack Query (React Query) for intelligent data caching and state management. This eliminates unnecessary API calls and provides a smooth, fast user experience.
Cache Configuration​
Global Defaults (src/main.jsx)​
{
staleTime: 5 * 60 * 1000, // Data fresh for 5 minutes
cacheTime: 10 * 60 * 1000, // Keep in cache for 10 minutes
refetchOnWindowFocus: false, // Don't refetch on tab focus
refetchOnReconnect: false, // Don't refetch on reconnect
retry: 1, // Only retry once on failure
}
Data-Specific Cache Times​
| Data Type | Fresh Time | Cache Time | Rationale |
|---|---|---|---|
| Tracks/Courses | 5 min | 30 min | Content rarely changes, admins update manually |
| Lessons | 10 min | 1 hour | Curriculum is static once published |
| Dashboard | 2 min | 10 min | Progress updates frequently during learning |
| Course Progress | 1 min | 5 min | Updates as users watch videos |
Available Query Hooks​
1. Tracks/Courses (src/hooks/queries/useTracks.js)​
import { useTracks, useTrack } from '../src/hooks/queries/useTracks';
// Get all tracks (cached for 30 minutes)
const { data: tracks, isLoading, error } = useTracks(userEmail);
// Get single track by ID
const { data: track } = useTrack(trackId);
2. Dashboard Data (src/hooks/queries/useEnrollments.js)​
import {
useDashboard,
useCourseProgress,
useInvalidateProgress
} from '../src/hooks/queries/useEnrollments';
// Get all dashboard data (enrollments, lessons, progress)
const { data: dashboardData, isLoading } = useDashboard(userId);
// Get progress for specific course
const { data: progress } = useCourseProgress(userId, trackId);
// Invalidate progress cache after video watching
const invalidateProgress = useInvalidateProgress();
invalidateProgress.mutate({ userId, trackId });
3. Lessons (src/hooks/queries/useLessons.js)​
import { useLessons } from '../src/hooks/queries/useLessons';
// Get lessons for a track (cached for 1 hour)
const { data: lessons, isLoading } = useLessons(trackId);
Benefits​
🚀 Performance​
- Instant page loads - Cached data displays immediately
- Reduced API calls - 80-90% fewer database queries
- Background updates - Data refreshes silently in the background
💰 Cost Savings​
- Lower server load - Fewer requests to Supabase
- Reduced bandwidth - Only fetch when data is stale
- Database efficiency - Fewer read operations
😊 Better UX​
- No loading spinners - Stale data shows while fetching fresh data
- Smooth navigation - Instant transitions between pages
- Offline resilience - Cached data available even with poor connection
Cache Invalidation​
Automatic Invalidation​
TanStack Query automatically invalidates based on:
staleTimeexpiry- Query key changes (e.g., different user/course)
Manual Invalidation​
import { useQueryClient } from '@tanstack/react-query';
const queryClient = useQueryClient();
// Invalidate specific query
queryClient.invalidateQueries({ queryKey: ['tracks'] });
// Invalidate all dashboard queries
queryClient.invalidateQueries({ queryKey: ['dashboard'] });
// Refetch immediately
queryClient.refetchQueries({ queryKey: ['courseProgress', userId, trackId] });
Development Tools​
React Query Devtools​
The devtools are automatically included in development mode. Click the TanStack Query icon in the bottom-right corner to:
- View all cached queries
- See query status (fresh, stale, fetching)
- Inspect query data
- Manually invalidate queries
- Monitor network requests
Migration Status​
✅ Migrated to TanStack Query​
- CoursesPage -
/courses - DashboardPage -
/dashboard
🔄 TODO​
- CourseDetailPage -
/courses/:slug - CourseViewPage -
/courses/:id/view - WaitlistPage -
/waitlist
Best Practices​
1. Use Specific Query Keys​
// Good - specific keys
['tracks', userEmail]
['courseProgress', userId, trackId]
// Bad - generic keys
['data']
['api']
2. Set Appropriate Stale Times​
// Static data - long stale time
staleTime: 30 * 60 * 1000 // 30 minutes
// Dynamic data - short stale time
staleTime: 1 * 60 * 1000 // 1 minute
3. Handle Loading States​
const { data, isLoading, error } = useTracks();
if (isLoading) return <LoadingSpinner />;
if (error) return <ErrorMessage error={error} />;
return <TracksList tracks={data} />;
4. Provide Default Values​
// Prevent undefined errors
const { data: tracks = [] } = useTracks();
const { data: progress = {} } = useCourseProgress();