fix(client): resolve all 13 exhaustive-deps warnings (R2b)
- BankSyncSection: handleBtSave read btLateGraceDays but it was missing from the useCallback deps -> saving could persist a STALE late-grace value (real bug). - ProfilePage: EditProfile sync effect now depends on [profile]. - Stabilized identity of derived arrays/objects feeding useMemo deps (they were recreated every render, defeating memoization): TrackerPage filters + rows, HealthPage bills, BankTransactionsPage transactions -> wrapped in useMemo. - BillModal + TransactionMatchingSection: intentional id/filter-scoped effects documented with a targeted eslint-disable + reason. - Removed 2 stale eslint-disable directives (confirm-dialog, useAuth). exhaustive-deps + rules-of-hooks now both 0. Build + client tests green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
5e267e4fa7
commit
b8d394061b
|
|
@ -176,6 +176,9 @@ export default function BillModal({ bill, initialBill, categories, onClose, onSa
|
|||
useEffect(() => {
|
||||
loadPayments();
|
||||
loadLinkedTransactions();
|
||||
// Intentional: reload only when the bill identity changes. The loaders are
|
||||
// recreated each render, so listing them would reload on every render.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [bill?.id]);
|
||||
|
||||
// Imported payments (via sync or a merchant-rule historical import) must
|
||||
|
|
|
|||
|
|
@ -380,7 +380,7 @@ export default function BankSyncSection({ onConnectionChange, cardProps = {} })
|
|||
} finally {
|
||||
setBtSaving(false);
|
||||
}
|
||||
}, [btEnabled, btAccountId, btPendingDays]);
|
||||
}, [btEnabled, btAccountId, btPendingDays, btLateGraceDays]);
|
||||
|
||||
const handleAcmSave = useCallback(async (next) => {
|
||||
setAcmSaving(true);
|
||||
|
|
|
|||
|
|
@ -256,6 +256,8 @@ export default function TransactionMatchingSection({ refreshKey, simplefinConn,
|
|||
};
|
||||
|
||||
useEffect(() => { loadBills(); }, []);
|
||||
// Intentional: reset + reload page 1 when the filter or refresh key changes.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
useEffect(() => { setSearch(''); setPage(1); loadTransactions(1, ''); }, [filter, refreshKey]);
|
||||
useEffect(() => { loadSuggestions(); }, [refreshKey]);
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -157,7 +157,6 @@ export function useConfirm() {
|
|||
onConfirm={handleConfirm}
|
||||
/>
|
||||
),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[state, handleConfirm, handleOpenChange]
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ export function AuthProvider({ children }) {
|
|||
}).catch(err => console.error('[useAuth] authMode check failed', err));
|
||||
|
||||
api.me().then(applyMeResponse).catch(() => setUser(null));
|
||||
}, []); // eslint-disable-line
|
||||
}, []);
|
||||
|
||||
const logout = async () => {
|
||||
await api.logout();
|
||||
|
|
|
|||
|
|
@ -545,7 +545,7 @@ export default function BankTransactionsPage() {
|
|||
}, [updateTransaction, loadLedger]);
|
||||
|
||||
const accounts = ledger?.accounts || [];
|
||||
const transactions = ledger?.transactions || [];
|
||||
const transactions = useMemo(() => ledger?.transactions || [], [ledger]);
|
||||
const summary = ledger?.summary || {};
|
||||
const total = Number(ledger?.total || 0);
|
||||
const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE));
|
||||
|
|
|
|||
|
|
@ -161,7 +161,7 @@ export default function HealthPage() {
|
|||
}, [load]);
|
||||
|
||||
const summary = data?.summary || {};
|
||||
const bills = data?.bills || [];
|
||||
const bills = useMemo(() => data?.bills || [], [data]);
|
||||
const sortedBills = useMemo(() => [...bills].sort((a, b) => {
|
||||
const aErrors = a.issues.filter(issue => issue.severity === 'error').length;
|
||||
const bErrors = b.issues.filter(issue => issue.severity === 'error').length;
|
||||
|
|
|
|||
|
|
@ -333,7 +333,7 @@ function EditProfile({ profile, onSaved }) {
|
|||
|
||||
useEffect(() => {
|
||||
setDisplayName(displayNameOf(profile));
|
||||
}, [profile.display_name, profile.displayName, profile.name]);
|
||||
}, [profile]);
|
||||
|
||||
const save = async () => {
|
||||
setSaving(true);
|
||||
|
|
|
|||
|
|
@ -205,7 +205,9 @@ export default function TrackerPage() {
|
|||
const year = Number(searchParams.get('year')) || now.getFullYear();
|
||||
const month = Number(searchParams.get('month')) || (now.getMonth() + 1);
|
||||
const search = searchParams.get('q') || '';
|
||||
const filters = {
|
||||
// Stable identity so the memo that filters rows on `filters` doesn't recompute
|
||||
// every render (a new object literal each render would defeat it).
|
||||
const filters = useMemo(() => ({
|
||||
category: searchParams.get('fc') || FILTER_ALL,
|
||||
cycle: searchParams.get('cy') || FILTER_ALL,
|
||||
autopay: searchParams.get('ap') === '1',
|
||||
|
|
@ -214,7 +216,7 @@ export default function TrackerPage() {
|
|||
unpaid: searchParams.get('un') === '1',
|
||||
overdue: searchParams.get('ov') === '1',
|
||||
debt: searchParams.get('de') === '1',
|
||||
};
|
||||
}), [searchParams]);
|
||||
const sortKey = normalizeTrackerSortKey(searchParams.get('sort') || TRACKER_SORT_DEFAULT);
|
||||
const hasSort = sortKey !== TRACKER_SORT_DEFAULT;
|
||||
const sortDir = hasSort
|
||||
|
|
@ -381,7 +383,7 @@ export default function TrackerPage() {
|
|||
updateParams({ year: n.getFullYear(), month: n.getMonth() + 1 });
|
||||
}
|
||||
|
||||
const rows = orderedRows || data?.rows || [];
|
||||
const rows = useMemo(() => orderedRows || data?.rows || [], [orderedRows, data]);
|
||||
const summary = data?.summary || {};
|
||||
const bankTracking = data?.bank_tracking;
|
||||
const cashflow = data?.cashflow;
|
||||
|
|
|
|||
Loading…
Reference in New Issue