diff --git a/client/hooks/useQueries.js b/client/hooks/useQueries.js index 39dc23f..8e3818a 100644 --- a/client/hooks/useQueries.js +++ b/client/hooks/useQueries.js @@ -97,3 +97,19 @@ export function useDeletedBills() { staleTime: 1000 * 60 * 5, }); } + +export function useSubscriptions() { + return useQuery({ + queryKey: ['subscriptions'], + queryFn: () => api.subscriptions(), + staleTime: 1000 * 60 * 5, + }); +} + +export function useSubscriptionRecommendations() { + return useQuery({ + queryKey: ['subscription-recommendations'], + queryFn: () => api.subscriptionRecommendations().then(r => r.recommendations || []), + staleTime: 1000 * 60 * 5, + }); +} diff --git a/client/pages/SubscriptionsPage.jsx b/client/pages/SubscriptionsPage.jsx index bbe910d..474ee92 100644 --- a/client/pages/SubscriptionsPage.jsx +++ b/client/pages/SubscriptionsPage.jsx @@ -1,5 +1,7 @@ import { useCallback, useEffect, useMemo, useOptimistic, useRef, useState } from 'react'; import { Link } from 'react-router-dom'; +import { useQueryClient } from '@tanstack/react-query'; +import { useSubscriptions, useSubscriptionRecommendations, useCategories, useBills } from '@/hooks/useQueries'; import { toast } from 'sonner'; import { Bell, @@ -993,12 +995,20 @@ function RecommendationDetailsDialog({ open, recommendation, categoryId, onClose } export default function SubscriptionsPage() { - const [data, setData] = useState({ summary: {}, subscriptions: [] }); - const [recommendations, setRecommendations] = useState([]); - const [categories, setCategories] = useState([]); - const [bills, setBills] = useState([]); - const [loading, setLoading] = useState(true); - const [recommendationsLoading, setRecommendationsLoading] = useState(true); + const queryClient = useQueryClient(); + const { data: subData, isPending: loading } = useSubscriptions(); + const data = subData || { summary: {}, subscriptions: [] }; + const { data: recommendations = [], isPending: recommendationsLoading } = useSubscriptionRecommendations(); + const { data: categories = [] } = useCategories(); + const { data: bills = [] } = useBills(); + // Optimistic updates write through the query cache so every existing call site + // (setData(prev => …), setBills(…), setRecommendations(…)) works unchanged. + const setData = useCallback((u) => queryClient.setQueryData(['subscriptions'], + prev => (typeof u === 'function' ? u(prev || { summary: {}, subscriptions: [] }) : u)), [queryClient]); + const setBills = useCallback((u) => queryClient.setQueryData(['bills'], + prev => (typeof u === 'function' ? u(prev || []) : u)), [queryClient]); + const setRecommendations = useCallback((u) => queryClient.setQueryData(['subscription-recommendations'], + prev => (typeof u === 'function' ? u(prev || []) : u)), [queryClient]); const [busyId, setBusyId] = useState(null); const [modal, setModal] = useState(null); const [matchTarget, setMatchTarget] = useState(null); @@ -1027,44 +1037,15 @@ export default function SubscriptionsPage() { return match?.id || null; }, [categories]); - const load = useCallback(async () => { - setLoading(true); - try { - const [subscriptionData, categoryData] = await Promise.all([ - api.subscriptions(), - api.categories(), - ]); - setData(subscriptionData); - setCategories(categoryData || []); - } catch (err) { - toast.error(err.message || 'Failed to load subscriptions.'); - } finally { - setLoading(false); - } - }, []); - - const loadRecommendations = useCallback(async () => { - setRecommendationsLoading(true); - try { - const result = await api.subscriptionRecommendations(); - setRecommendations(result.recommendations || []); - } catch (err) { - toast.error(err.message || 'Failed to scan subscription recommendations.'); - } finally { - setRecommendationsLoading(false); - } - }, []); - - useEffect(() => { - load(); - loadRecommendations(); - api.allBills() - .then(b => setBills(Array.isArray(b) ? b : [])) - .catch(err => { - console.error('[SubscriptionsPage] failed to load bills', err); - toast.error(err.message || 'Bills could not be loaded for subscription linking.'); - }); - }, [load, loadRecommendations]); + const load = useCallback(() => Promise.all([ + queryClient.invalidateQueries({ queryKey: ['subscriptions'] }), + queryClient.invalidateQueries({ queryKey: ['categories'] }), + queryClient.invalidateQueries({ queryKey: ['bills'] }), + ]), [queryClient]); + const loadRecommendations = useCallback( + () => queryClient.invalidateQueries({ queryKey: ['subscription-recommendations'] }), + [queryClient], + ); useEffect(() => { localStorage.setItem(SUBSCRIPTION_SORT_KEY, subscriptionSort);