feat(tracker): prefetch adjacent month on nav hover for instant switching (P2)
usePrefetchTracker() warms the ['tracker', y, m] cache when the user hovers/ focuses the prev/next month buttons, so clicking is instant (no round-trip). No-op if already cached and fresh. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
2793927a5c
commit
e941f05cd6
|
|
@ -68,6 +68,19 @@ export function useInvalidateTrackerData() {
|
||||||
}, [queryClient]);
|
}, [queryClient]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prefetch a tracker month into the cache (e.g. on month-nav hover) so switching
|
||||||
|
// to it is instant. No-op if it's already cached and fresh.
|
||||||
|
export function usePrefetchTracker() {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useCallback((year, month) => {
|
||||||
|
queryClient.prefetchQuery({
|
||||||
|
queryKey: ['tracker', year, month],
|
||||||
|
queryFn: () => api.tracker(year, month),
|
||||||
|
staleTime: 1000 * 60 * 5,
|
||||||
|
});
|
||||||
|
}, [queryClient]);
|
||||||
|
}
|
||||||
|
|
||||||
// ── Page data (migrated off manual useEffect + load()) ───────────────────────
|
// ── Page data (migrated off manual useEffect + load()) ───────────────────────
|
||||||
// The queryKey encodes the params, so React Query handles caching, request
|
// The queryKey encodes the params, so React Query handles caching, request
|
||||||
// dedup, cancellation, and out-of-order responses — no manual sequence guards.
|
// dedup, cancellation, and out-of-order responses — no manual sequence guards.
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { useSearchParams } from 'react-router-dom';
|
||||||
import { ChevronLeft, ChevronRight, AlertCircle, CheckCircle2, Plus, Search, RefreshCw, Landmark, ArrowUpToLine, ArrowUp, ArrowDown, BellOff, EyeOff, Settings2 } from 'lucide-react';
|
import { ChevronLeft, ChevronRight, AlertCircle, CheckCircle2, Plus, Search, RefreshCw, Landmark, ArrowUpToLine, ArrowUp, ArrowDown, BellOff, EyeOff, Settings2 } from 'lucide-react';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { api } from '@/api.js';
|
import { api } from '@/api.js';
|
||||||
import { useTracker, useDriftReport, useInvalidateTrackerData } from '@/hooks/useQueries';
|
import { useTracker, useDriftReport, useInvalidateTrackerData, usePrefetchTracker } from '@/hooks/useQueries';
|
||||||
import { useSearchPanelPreference } from '@/hooks/useSearchPanelPreference';
|
import { useSearchPanelPreference } from '@/hooks/useSearchPanelPreference';
|
||||||
import BillModal from '@/components/BillModal';
|
import BillModal from '@/components/BillModal';
|
||||||
import { makeBillDraft } from '@/lib/billDrafts';
|
import { makeBillDraft } from '@/lib/billDrafts';
|
||||||
|
|
@ -301,12 +301,23 @@ export default function TrackerPage() {
|
||||||
return () => window.removeEventListener('tracker:late-attributions', handler);
|
return () => window.removeEventListener('tracker:late-attributions', handler);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
function navigate(delta) {
|
function adjacentMonth(delta) {
|
||||||
let nm = month + delta;
|
let nm = month + delta;
|
||||||
let ny = year;
|
let ny = year;
|
||||||
if (nm > 12) { ny += 1; nm = 1; }
|
if (nm > 12) { ny += 1; nm = 1; }
|
||||||
if (nm < 1) { ny -= 1; nm = 12; }
|
if (nm < 1) { ny -= 1; nm = 12; }
|
||||||
updateParams({ year: ny, month: nm });
|
return { year: ny, month: nm };
|
||||||
|
}
|
||||||
|
|
||||||
|
function navigate(delta) {
|
||||||
|
updateParams(adjacentMonth(delta));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prefetch the adjacent month on hover/focus so clicking prev/next is instant.
|
||||||
|
const prefetchTracker = usePrefetchTracker();
|
||||||
|
function prefetchAdjacent(delta) {
|
||||||
|
const { year: ny, month: nm } = adjacentMonth(delta);
|
||||||
|
prefetchTracker(ny, nm);
|
||||||
}
|
}
|
||||||
|
|
||||||
function bankSyncMessage(result) {
|
function bankSyncMessage(result) {
|
||||||
|
|
@ -651,6 +662,8 @@ export default function TrackerPage() {
|
||||||
<Button
|
<Button
|
||||||
size="icon" variant="ghost"
|
size="icon" variant="ghost"
|
||||||
onClick={() => navigate(-1)}
|
onClick={() => navigate(-1)}
|
||||||
|
onMouseEnter={() => prefetchAdjacent(-1)}
|
||||||
|
onFocus={() => prefetchAdjacent(-1)}
|
||||||
className="h-7 w-7 hover:bg-white/5"
|
className="h-7 w-7 hover:bg-white/5"
|
||||||
aria-label="Previous month"
|
aria-label="Previous month"
|
||||||
>
|
>
|
||||||
|
|
@ -662,6 +675,8 @@ export default function TrackerPage() {
|
||||||
<Button
|
<Button
|
||||||
size="icon" variant="ghost"
|
size="icon" variant="ghost"
|
||||||
onClick={() => navigate(1)}
|
onClick={() => navigate(1)}
|
||||||
|
onMouseEnter={() => prefetchAdjacent(1)}
|
||||||
|
onFocus={() => prefetchAdjacent(1)}
|
||||||
className="h-7 w-7 hover:bg-white/5"
|
className="h-7 w-7 hover:bg-white/5"
|
||||||
aria-label="Next month"
|
aria-label="Next month"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue