Add data prefetching to eliminate skeleton flash on tab switch

Prefetches all main page queries (dashboard, upcoming, todos, reminders,
projects, people, locations) in parallel when the app unlocks, so the
TanStack Query cache is warm before the user navigates to each tab.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Kyle 2026-03-12 22:19:08 +08:00
parent 18a2c1314a
commit 379cc74387
2 changed files with 64 additions and 0 deletions

View File

@ -2,6 +2,7 @@ import { useState } from 'react';
import { Outlet } from 'react-router-dom';
import { Menu } from 'lucide-react';
import { useTheme } from '@/hooks/useTheme';
import { usePrefetch } from '@/hooks/usePrefetch';
import { AlertsProvider } from '@/hooks/useAlerts';
import { LockProvider, useLock } from '@/hooks/useLock';
import { NotificationProvider } from '@/hooks/useNotifications';
@ -16,6 +17,7 @@ function AppContent({ mobileOpen, setMobileOpen }: {
setMobileOpen: (v: boolean) => void;
}) {
const { isLocked, isLockResolved } = useLock();
usePrefetch(isLockResolved && !isLocked);
const [collapsed, setCollapsed] = useState(() => {
try { return JSON.parse(localStorage.getItem('umbra-sidebar-collapsed') || 'false'); }
catch { return false; }

View File

@ -0,0 +1,62 @@
import { useEffect, useRef } from 'react';
import { useQueryClient } from '@tanstack/react-query';
import api from '@/lib/api';
import { useSettings } from './useSettings';
/**
* Prefetches main page data in the background after the app unlocks.
* Ensures cache is warm before the user navigates to each tab,
* eliminating the skeleton flash on first visit.
*/
export function usePrefetch(enabled: boolean) {
const queryClient = useQueryClient();
const { settings } = useSettings();
const hasPrefetched = useRef(false);
useEffect(() => {
if (!enabled || hasPrefetched.current) return;
hasPrefetched.current = true;
const now = new Date();
const clientDate = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
const days = settings?.upcoming_days || 7;
// Prefetch all main page queries (no-op if already cached and fresh)
queryClient.prefetchQuery({
queryKey: ['dashboard'],
queryFn: () => api.get(`/dashboard?client_date=${clientDate}`).then(r => r.data),
staleTime: 60_000,
});
queryClient.prefetchQuery({
queryKey: ['upcoming', days],
queryFn: () => api.get(`/upcoming?days=${days}&client_date=${clientDate}`).then(r => r.data),
staleTime: 60_000,
});
queryClient.prefetchQuery({
queryKey: ['todos'],
queryFn: () => api.get('/todos').then(r => r.data),
});
queryClient.prefetchQuery({
queryKey: ['reminders'],
queryFn: () => api.get('/reminders').then(r => r.data),
});
queryClient.prefetchQuery({
queryKey: ['projects'],
queryFn: () => api.get('/projects').then(r => r.data),
});
queryClient.prefetchQuery({
queryKey: ['people'],
queryFn: () => api.get('/people').then(r => r.data),
});
queryClient.prefetchQuery({
queryKey: ['locations'],
queryFn: () => api.get('/locations').then(r => r.data),
});
}, [enabled, queryClient, settings?.upcoming_days]);
}