Skip to main content

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 TypeFresh TimeCache TimeRationale
Tracks/Courses5 min30 minContent rarely changes, admins update manually
Lessons10 min1 hourCurriculum is static once published
Dashboard2 min10 minProgress updates frequently during learning
Course Progress1 min5 minUpdates 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:

  • staleTime expiry
  • 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();

Learn More​