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(() => {
|
useEffect(() => {
|
||||||
loadPayments();
|
loadPayments();
|
||||||
loadLinkedTransactions();
|
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]);
|
}, [bill?.id]);
|
||||||
|
|
||||||
// Imported payments (via sync or a merchant-rule historical import) must
|
// Imported payments (via sync or a merchant-rule historical import) must
|
||||||
|
|
|
||||||
|
|
@ -380,7 +380,7 @@ export default function BankSyncSection({ onConnectionChange, cardProps = {} })
|
||||||
} finally {
|
} finally {
|
||||||
setBtSaving(false);
|
setBtSaving(false);
|
||||||
}
|
}
|
||||||
}, [btEnabled, btAccountId, btPendingDays]);
|
}, [btEnabled, btAccountId, btPendingDays, btLateGraceDays]);
|
||||||
|
|
||||||
const handleAcmSave = useCallback(async (next) => {
|
const handleAcmSave = useCallback(async (next) => {
|
||||||
setAcmSaving(true);
|
setAcmSaving(true);
|
||||||
|
|
|
||||||
|
|
@ -256,6 +256,8 @@ export default function TransactionMatchingSection({ refreshKey, simplefinConn,
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => { loadBills(); }, []);
|
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(() => { setSearch(''); setPage(1); loadTransactions(1, ''); }, [filter, refreshKey]);
|
||||||
useEffect(() => { loadSuggestions(); }, [refreshKey]);
|
useEffect(() => { loadSuggestions(); }, [refreshKey]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
||||||
|
|
@ -157,7 +157,6 @@ export function useConfirm() {
|
||||||
onConfirm={handleConfirm}
|
onConfirm={handleConfirm}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
[state, handleConfirm, handleOpenChange]
|
[state, handleConfirm, handleOpenChange]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ export function AuthProvider({ children }) {
|
||||||
}).catch(err => console.error('[useAuth] authMode check failed', err));
|
}).catch(err => console.error('[useAuth] authMode check failed', err));
|
||||||
|
|
||||||
api.me().then(applyMeResponse).catch(() => setUser(null));
|
api.me().then(applyMeResponse).catch(() => setUser(null));
|
||||||
}, []); // eslint-disable-line
|
}, []);
|
||||||
|
|
||||||
const logout = async () => {
|
const logout = async () => {
|
||||||
await api.logout();
|
await api.logout();
|
||||||
|
|
|
||||||
|
|
@ -545,7 +545,7 @@ export default function BankTransactionsPage() {
|
||||||
}, [updateTransaction, loadLedger]);
|
}, [updateTransaction, loadLedger]);
|
||||||
|
|
||||||
const accounts = ledger?.accounts || [];
|
const accounts = ledger?.accounts || [];
|
||||||
const transactions = ledger?.transactions || [];
|
const transactions = useMemo(() => ledger?.transactions || [], [ledger]);
|
||||||
const summary = ledger?.summary || {};
|
const summary = ledger?.summary || {};
|
||||||
const total = Number(ledger?.total || 0);
|
const total = Number(ledger?.total || 0);
|
||||||
const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE));
|
const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE));
|
||||||
|
|
|
||||||
|
|
@ -161,7 +161,7 @@ export default function HealthPage() {
|
||||||
}, [load]);
|
}, [load]);
|
||||||
|
|
||||||
const summary = data?.summary || {};
|
const summary = data?.summary || {};
|
||||||
const bills = data?.bills || [];
|
const bills = useMemo(() => data?.bills || [], [data]);
|
||||||
const sortedBills = useMemo(() => [...bills].sort((a, b) => {
|
const sortedBills = useMemo(() => [...bills].sort((a, b) => {
|
||||||
const aErrors = a.issues.filter(issue => issue.severity === 'error').length;
|
const aErrors = a.issues.filter(issue => issue.severity === 'error').length;
|
||||||
const bErrors = b.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(() => {
|
useEffect(() => {
|
||||||
setDisplayName(displayNameOf(profile));
|
setDisplayName(displayNameOf(profile));
|
||||||
}, [profile.display_name, profile.displayName, profile.name]);
|
}, [profile]);
|
||||||
|
|
||||||
const save = async () => {
|
const save = async () => {
|
||||||
setSaving(true);
|
setSaving(true);
|
||||||
|
|
|
||||||
|
|
@ -205,7 +205,9 @@ export default function TrackerPage() {
|
||||||
const year = Number(searchParams.get('year')) || now.getFullYear();
|
const year = Number(searchParams.get('year')) || now.getFullYear();
|
||||||
const month = Number(searchParams.get('month')) || (now.getMonth() + 1);
|
const month = Number(searchParams.get('month')) || (now.getMonth() + 1);
|
||||||
const search = searchParams.get('q') || '';
|
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,
|
category: searchParams.get('fc') || FILTER_ALL,
|
||||||
cycle: searchParams.get('cy') || FILTER_ALL,
|
cycle: searchParams.get('cy') || FILTER_ALL,
|
||||||
autopay: searchParams.get('ap') === '1',
|
autopay: searchParams.get('ap') === '1',
|
||||||
|
|
@ -214,7 +216,7 @@ export default function TrackerPage() {
|
||||||
unpaid: searchParams.get('un') === '1',
|
unpaid: searchParams.get('un') === '1',
|
||||||
overdue: searchParams.get('ov') === '1',
|
overdue: searchParams.get('ov') === '1',
|
||||||
debt: searchParams.get('de') === '1',
|
debt: searchParams.get('de') === '1',
|
||||||
};
|
}), [searchParams]);
|
||||||
const sortKey = normalizeTrackerSortKey(searchParams.get('sort') || TRACKER_SORT_DEFAULT);
|
const sortKey = normalizeTrackerSortKey(searchParams.get('sort') || TRACKER_SORT_DEFAULT);
|
||||||
const hasSort = sortKey !== TRACKER_SORT_DEFAULT;
|
const hasSort = sortKey !== TRACKER_SORT_DEFAULT;
|
||||||
const sortDir = hasSort
|
const sortDir = hasSort
|
||||||
|
|
@ -381,7 +383,7 @@ export default function TrackerPage() {
|
||||||
updateParams({ year: n.getFullYear(), month: n.getMonth() + 1 });
|
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 summary = data?.summary || {};
|
||||||
const bankTracking = data?.bank_tracking;
|
const bankTracking = data?.bank_tracking;
|
||||||
const cashflow = data?.cashflow;
|
const cashflow = data?.cashflow;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue