087fbdd176
Budget.Core: entities, DTOs, enums, FrequencyCalculator (no EF/ASP.NET deps) Budget.Infrastructure: AppDbContext, migrations, BudgetAuthorizationService Budget.Api: controllers, middleware, Program.cs — references both projects EF and Npgsql packages moved to Infrastructure; Api retains only JwtBearer, HealthChecks, and EF.Design (needed for dotnet ef CLI). Dockerfile updated to copy all three project directories before publishing. Migration namespaces updated from Budget.Api.Data.* to Budget.Infrastructure.Data.* and model type strings updated to Budget.Core.Models.* in the snapshot. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5.0 KiB
5.0 KiB
Plan: Add TanStack React Query
Goal
Replace the ad-hoc useEffect + useState fetch pattern with @tanstack/react-query.
All data fetching moves into typed useQuery / useMutation hooks. A global MutationCache
fires toasts on success and error so pages don't have to wire that up individually.
Prerequisite: Plan #11 (react-oidc-context) must be complete, as TokenSync replaces
the setTokenProvider wiring that pages currently rely on.
Current state
Each page manages its own loading/error state with useState and fetches in useEffect:
const [budgets, setBudgets] = useState<BudgetDto[]>([]);
useEffect(() => {
api.get<BudgetDto[]>('/api/budgets').then(setBudgets);
}, []);
Mutations are plain async calls with no error handling or cache invalidation.
Target state
src/api/
client.ts — unchanged fetch wrapper
queryClient.ts — QueryClient with MutationCache for global toasts
budgets.ts — useQuery/useMutation hooks for budgets
incomes.ts — hooks for incomes
outgos.ts — hooks for outgos
shares.ts — hooks for shares
summary.ts — hooks for summary
Pages become thin: call a hook, render data — no fetch logic inline.
Steps
Phase 1 — Install and configure QueryClient
npm install @tanstack/react-queryinsrc/Budget.Client/.- Create
src/Budget.Client/src/api/queryClient.ts:- Create a
QueryClientwith aMutationCachethat readsmeta.successMessageandmeta.errorMessagefrom each mutation and fires the existingtoastutility ononSuccess/onError. - Export the
queryClientsingleton.
- Create a
- Wrap the app in
<QueryClientProvider client={queryClient}>inmain.tsx(inside<AuthProvider>).
Phase 2 — Create domain hook files
For each domain, create a hooks file following this pattern:
src/api/budgets.ts
export function useBudgets() {
return useQuery({ queryKey: ['budgets'], queryFn: () => api.get<BudgetDto[]>('/api/budgets') });
}
export function useCreateBudget() {
const qc = useQueryClient();
return useMutation({
mutationFn: (req: CreateBudgetRequest) => api.post<BudgetDto>('/api/budgets', req),
onSuccess: () => qc.invalidateQueries({ queryKey: ['budgets'] }),
meta: { successMessage: 'Budget created', errorMessage: 'Failed to create budget' },
});
}
export function useUpdateBudget(id: string) { ... }
export function useDeleteBudget() { ... }
- Create
src/api/budgets.ts— hooks for List, Create, Update, Delete. - Create
src/api/incomes.ts— hooks for List, Create, Update, Delete, Reorder. Query key:['budgets', budgetId, 'incomes']. - Create
src/api/outgos.ts— hooks for List, Create, Update, Delete, Reorder. Query key:['budgets', budgetId, 'outgos']. Also hooks forcategoriesandpayment-sourceslookups. - Create
src/api/shares.ts— hooks for List, Add, Update, Revoke. Query key:['budgets', budgetId, 'shares']. - Create
src/api/summary.ts— hooks for Get and UpdateTaxRate. Query key:['budgets', budgetId, 'summary'].
Phase 3 — Update pages to use hooks
BudgetsPage.tsx— replaceuseEffect+useStatewithuseBudgets()anduseCreateBudget().IncomePage.tsx— replace withuseIncomes(),useCreateIncome(),useUpdateIncome(),useDeleteIncome(),useReorderIncomes().OutgoPage.tsx— replace with outgo hooks + category/payment-source lookup hooks.SummaryPage.tsx— replace withuseSummary()anduseUpdateTaxRate().SettingsPage.tsx— replace withuseShares(),useAddShare(),useUpdateShare(),useRevokeShare().
Phase 4 — Clean up
- Remove
setTokenProviderfrommain.tsxif it is no longer wired there (it should now live only inTokenSync.tsxfrom plan #11). - Remove unused
useEffect/useStateimports from pages. npm run build— zero TypeScript errors.
Key decisions
api/client.tsis not changed — hooks wrap it, they don't replace it.- Cache invalidation strategy: mutations invalidate the narrowest relevant query key
(e.g., a deleted income invalidates
['budgets', id, 'incomes'], not all budgets). - No optimistic updates in this plan — add separately if needed.
meta.successMessageandmeta.errorMessageare typed by augmenting the@tanstack/react-querymodule'sRegisterinterface so TypeScript validates them.
Files affected
package.jsonsrc/Budget.Client/src/main.tsx- New:
src/Budget.Client/src/api/queryClient.ts - New:
src/Budget.Client/src/api/budgets.ts - New:
src/Budget.Client/src/api/incomes.ts - New:
src/Budget.Client/src/api/outgos.ts - New:
src/Budget.Client/src/api/shares.ts - New:
src/Budget.Client/src/api/summary.ts src/Budget.Client/src/pages/BudgetsPage.tsxsrc/Budget.Client/src/pages/IncomePage.tsxsrc/Budget.Client/src/pages/OutgoPage.tsxsrc/Budget.Client/src/pages/SummaryPage.tsxsrc/Budget.Client/src/pages/SettingsPage.tsx