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:
parent
18a2c1314a
commit
379cc74387
@ -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; }
|
||||
|
||||
62
frontend/src/hooks/usePrefetch.ts
Normal file
62
frontend/src/hooks/usePrefetch.ts
Normal 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]);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user