From 6fa4d673cf0edc2b8ba4ef1f97292269615bb233 Mon Sep 17 00:00:00 2001 From: Joel Jeremy Marquez Date: Mon, 23 Feb 2026 23:26:25 +0000 Subject: [PATCH] Fix refetch happening when switching budget files --- .../src/budgetfiles/budgetfilesSlice.ts | 23 +++++++++++++++++-- .../desktop-client/src/components/App.tsx | 4 +--- .../src/components/FinancesApp.tsx | 3 --- packages/desktop-client/src/prefs/queries.ts | 18 +++++++++++---- 4 files changed, 35 insertions(+), 13 deletions(-) diff --git a/packages/desktop-client/src/budgetfiles/budgetfilesSlice.ts b/packages/desktop-client/src/budgetfiles/budgetfilesSlice.ts index ea76c44824..c8a746b3a7 100644 --- a/packages/desktop-client/src/budgetfiles/budgetfilesSlice.ts +++ b/packages/desktop-client/src/budgetfiles/budgetfilesSlice.ts @@ -1,5 +1,6 @@ import { createSlice } from '@reduxjs/toolkit'; import type { PayloadAction } from '@reduxjs/toolkit'; +import { type QueryClient } from '@tanstack/react-query'; import { t } from 'i18next'; import { send } from 'loot-core/platform/client/connection'; @@ -98,16 +99,34 @@ export const loadBudget = createAppAsyncThunk( }, ); +function invalidateClosedBudgetQueries(queryClient: QueryClient) { + // Invalidate all queries but do not cause a refetch. + // This is because we want to clear out all the budget data from the queries, + // but we don't want to trigger a bunch of error states from the queries trying + // to fetch data for a budget that is now closed. The next time a budget is loaded, + // the queries will refetch with the correct budget id. + queryClient.invalidateQueries({ + refetchType: 'none', + }); + + // Invalidate the metadata query since the budget is now closed. + // We want to cause a refetch so that the app can update to show the correct state + // (e.g. show the manager page if no budget is open). + queryClient.invalidateQueries({ + queryKey: prefQueries.listMetadata().queryKey, + }); +} + export const closeBudget = createAppAsyncThunk( `${sliceName}/closeBudget`, async (_, { dispatch, extra: { queryClient } }) => { const prefs = await queryClient.ensureQueryData(prefQueries.listMetadata()); if (prefs && prefs.id) { dispatch(resetApp()); - queryClient.clear(); dispatch(setAppState({ loadingText: t('Closing...') })); await send('close-budget'); dispatch(setAppState({ loadingText: null })); + invalidateClosedBudgetQueries(queryClient); if (localStorage.getItem('SharedArrayBufferOverride')) { window.location.reload(); } @@ -121,7 +140,7 @@ export const closeBudgetUI = createAppAsyncThunk( const prefs = await queryClient.ensureQueryData(prefQueries.listMetadata()); if (prefs && prefs.id) { dispatch(resetApp()); - queryClient.clear(); + invalidateClosedBudgetQueries(queryClient); } }, ); diff --git a/packages/desktop-client/src/components/App.tsx b/packages/desktop-client/src/components/App.tsx index e3617dfd45..3a1c0e9f01 100644 --- a/packages/desktop-client/src/components/App.tsx +++ b/packages/desktop-client/src/components/App.tsx @@ -92,9 +92,7 @@ function AppInner() { loadingText: t('Loading global preferences...'), }), ); - void queryClient.invalidateQueries({ - queryKey: prefQueries.listGlobal().queryKey, - }); + void queryClient.prefetchQuery(prefQueries.listGlobal()); // Open the last opened budget, if any dispatch( diff --git a/packages/desktop-client/src/components/FinancesApp.tsx b/packages/desktop-client/src/components/FinancesApp.tsx index beedf074fa..e137be3db5 100644 --- a/packages/desktop-client/src/components/FinancesApp.tsx +++ b/packages/desktop-client/src/components/FinancesApp.tsx @@ -192,9 +192,6 @@ export function FinancesApp() { const scrollableRef = useRef(null); - // Prefetch preferences - usePrefetchQuery(prefQueries.list()); - return ( diff --git a/packages/desktop-client/src/prefs/queries.ts b/packages/desktop-client/src/prefs/queries.ts index cb5c95733b..14b6e64aff 100644 --- a/packages/desktop-client/src/prefs/queries.ts +++ b/packages/desktop-client/src/prefs/queries.ts @@ -12,7 +12,7 @@ import type { import { setI18NextLanguage } from '@desktop-client/i18n'; export type AllPrefs = { - local: MetadataPrefs; + metadata: MetadataPrefs; global: GlobalPrefs; synced: SyncedPrefs; server: ServerPrefs; @@ -25,7 +25,7 @@ export const prefQueries = { queryOptions({ queryKey: [...prefQueries.lists(), 'all'], queryFn: async ({ client }) => { - const [localPrefs, globalPrefs, syncedPrefs] = await Promise.all([ + const [metadataPrefs, globalPrefs, syncedPrefs] = await Promise.all([ client.ensureQueryData(prefQueries.listMetadata()), client.ensureQueryData(prefQueries.listGlobal()), client.ensureQueryData(prefQueries.listSynced()), @@ -43,14 +43,14 @@ export const prefQueries = { setI18NextLanguage(globalPrefs.language ?? ''); return { - local: localPrefs, + metadata: metadataPrefs, global: globalPrefs, synced: syncedPrefs, server: {}, // Server prefs are loaded separately }; }, placeholderData: { - local: {}, + metadata: {}, global: {}, synced: {}, server: {}, @@ -81,7 +81,15 @@ export const prefQueries = { listSynced: () => queryOptions({ queryKey: [...prefQueries.lists(), 'synced'], - queryFn: async () => { + queryFn: async ({ client }) => { + const metadataPrefs = await client.getQueryData( + prefQueries.listMetadata().queryKey, + ); + // Synced prefs are budget-specific, so if we don't have + // a budget loaded, just return an empty object. + if (!metadataPrefs?.id) { + return {}; + } return await send('preferences/get'); }, placeholderData: {},