B1: React Query deeper adoption (P1–P3) #86

Closed
null wants to merge 340 commits from branch/b1-react-query into main
2 changed files with 39 additions and 33 deletions
Showing only changes of commit a37697d492 - Show all commits

View File

@ -98,6 +98,18 @@ export function useDeletedBills() {
});
}
export function useSummary(year, month) {
return useQuery({
queryKey: ['summary', year, month],
queryFn: () => api.summary(year, month),
staleTime: 1000 * 60 * 2,
placeholderData: keepPreviousData,
// Editable form fields (starting amounts, income) are seeded from this
// result via an effect; don't let a focus refetch reset a mid-edit.
refetchOnWindowFocus: false,
});
}
export function useSubscriptions() {
return useQuery({
queryKey: ['subscriptions'],

View File

@ -1,4 +1,6 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useQueryClient } from '@tanstack/react-query';
import { useSummary } from '@/hooks/useQueries';
import { toast } from 'sonner';
import {
ArrowDown,
@ -235,10 +237,12 @@ function ExpenseRow({ expense, moveControls, dragProps }) {
export default function SummaryPage() {
const [selected, setSelected] = useState(selectedFromToday);
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const queryClient = useQueryClient();
const { data = null, isPending: loading, error: queryError } = useSummary(selected.year, selected.month);
const setData = useCallback((u) => queryClient.setQueryData(['summary', selected.year, selected.month],
prev => (typeof u === 'function' ? u(prev) : u)), [queryClient, selected.year, selected.month]);
const [saving, setSaving] = useState(false);
const [error, setError] = useState('');
const error = queryError ? (queryError.message || 'Summary could not be loaded.') : '';
const [startingFirst, setStartingFirst] = useState('0');
const [startingFifteenth, setStartingFifteenth] = useState('0');
const [startingOther, setStartingOther] = useState('0');
@ -250,37 +254,27 @@ export default function SummaryPage() {
const [dropTargetId, setDropTargetId] = useState(null);
const [movingBillId, setMovingBillId] = useState(null);
const requestSeq = useRef(0);
const loadSummary = useCallback(async () => {
// Ignore out-of-order responses: only the newest month load updates state.
const seq = ++requestSeq.current;
setLoading(true);
setError('');
try {
const result = await api.summary(selected.year, selected.month);
if (seq !== requestSeq.current) return; // superseded by a newer request
setData(result);
setStartingFirst(String(result.starting_amounts?.first_amount ?? 0));
setStartingFifteenth(String(result.starting_amounts?.fifteenth_amount ?? 0));
setStartingOther(String(result.starting_amounts?.other_amount ?? 0));
setEditingStarting(false);
setIncomeAmount(String(result.income?.amount ?? 0));
setIncomeLabel(result.income?.label || 'Salary');
setEditingIncome(false);
setDraggingId(null);
setDropTargetId(null);
setMovingBillId(null);
} catch (err) {
if (seq === requestSeq.current) setError(err.message || 'Summary could not be loaded.');
toast.error(err.message || 'Summary could not be loaded.');
} finally {
if (seq === requestSeq.current) setLoading(false);
}
}, [selected.month, selected.year]);
const loadSummary = useCallback(
() => queryClient.invalidateQueries({ queryKey: ['summary', selected.year, selected.month] }),
[queryClient, selected.month, selected.year],
);
// Seed the editable form fields (starting amounts, income) from the loaded
// summary and reset transient edit/drag state whenever the data changes
// this is what loadSummary used to do inline.
useEffect(() => {
loadSummary();
}, [loadSummary]);
if (!data) return;
setStartingFirst(String(data.starting_amounts?.first_amount ?? 0));
setStartingFifteenth(String(data.starting_amounts?.fifteenth_amount ?? 0));
setStartingOther(String(data.starting_amounts?.other_amount ?? 0));
setEditingStarting(false);
setIncomeAmount(String(data.income?.amount ?? 0));
setIncomeLabel(data.income?.label || 'Salary');
setEditingIncome(false);
setDraggingId(null);
setDropTargetId(null);
setMovingBillId(null);
}, [data]);
const summary = data?.summary || {};
const expenses = data?.expenses || [];