Fix react-hooks/exhaustive-deps error on TransactionsTable.jsx (#4268)

* Fix react-hooks/exhaustive-deps error on TransactionsTable.jsx

* Release notes

* Fix lint
This commit is contained in:
Joel Jeremy Marquez
2025-02-19 07:06:41 -08:00
committed by GitHub
parent a085945898
commit d902c38253
4 changed files with 149 additions and 99 deletions

View File

@@ -792,9 +792,6 @@ export default [
'packages/desktop-client/src/components/spreadsheet/useSheetValue.ts', 'packages/desktop-client/src/components/spreadsheet/useSheetValue.ts',
'packages/desktop-client/src/components/table.tsx', 'packages/desktop-client/src/components/table.tsx',
'packages/desktop-client/src/components/transactions/TransactionList.jsx', 'packages/desktop-client/src/components/transactions/TransactionList.jsx',
'packages/desktop-client/src/components/transactions/TransactionsTable.jsx',
'packages/desktop-client/src/components/transactions/TransactionsTable.test.jsx',
// 'packages/desktop-client/src/hooks/useAccounts.ts',
'packages/desktop-client/src/hooks/useCategories.ts', 'packages/desktop-client/src/hooks/useCategories.ts',
], ],

View File

@@ -7,7 +7,6 @@ import React, {
useRef, useRef,
useMemo, useMemo,
useCallback, useCallback,
useLayoutEffect,
useEffect, useEffect,
} from 'react'; } from 'react';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
@@ -1856,28 +1855,35 @@ function TransactionTableInner({
setScrollWidth(!width ? 0 : width); setScrollWidth(!width ? 0 : width);
} }
const {
onCloseAddTransaction: onCloseAddTransactionProp,
onNavigateToTransferAccount: onNavigateToTransferAccountProp,
onNavigateToSchedule: onNavigateToScheduleProp,
onNotesTagClick: onNotesTagClickProp,
} = props;
const onNavigateToTransferAccount = useCallback( const onNavigateToTransferAccount = useCallback(
accountId => { accountId => {
props.onCloseAddTransaction(); onCloseAddTransactionProp();
props.onNavigateToTransferAccount(accountId); onNavigateToTransferAccountProp(accountId);
}, },
[props.onCloseAddTransaction, props.onNavigateToTransferAccount], [onCloseAddTransactionProp, onNavigateToTransferAccountProp],
); );
const onNavigateToSchedule = useCallback( const onNavigateToSchedule = useCallback(
scheduleId => { scheduleId => {
props.onCloseAddTransaction(); onCloseAddTransactionProp();
props.onNavigateToSchedule(scheduleId); onNavigateToScheduleProp(scheduleId);
}, },
[props.onCloseAddTransaction, props.onNavigateToSchedule], [onCloseAddTransactionProp, onNavigateToScheduleProp],
); );
const onNotesTagClick = useCallback( const onNotesTagClick = useCallback(
noteTag => { noteTag => {
props.onCloseAddTransaction(); onCloseAddTransactionProp();
props.onNotesTagClick(noteTag); onNotesTagClickProp(noteTag);
}, },
[props.onCloseAddTransaction, props.onNotesTagClick], [onCloseAddTransactionProp, onNotesTagClickProp],
); );
useEffect(() => { useEffect(() => {
@@ -2113,6 +2119,7 @@ export const TransactionTable = forwardRef((props, ref) => {
const [newTransactions, setNewTransactions] = useState(null); const [newTransactions, setNewTransactions] = useState(null);
const [prevIsAdding, setPrevIsAdding] = useState(false); const [prevIsAdding, setPrevIsAdding] = useState(false);
const splitsExpanded = useSplitsExpanded(); const splitsExpanded = useSplitsExpanded();
const splitsExpandedDispatch = splitsExpanded.dispatch;
const prevSplitsExpanded = useRef(null); const prevSplitsExpanded = useRef(null);
const tableRef = useRef(null); const tableRef = useRef(null);
@@ -2191,6 +2198,8 @@ export const TransactionTable = forwardRef((props, ref) => {
); );
}, [props.transactions, props.payees, props.accounts]); }, [props.transactions, props.payees, props.accounts]);
const hasPrevSplitsExpanded = prevSplitsExpanded.current;
useEffect(() => { useEffect(() => {
// If it's anchored that means we've also disabled animations. To // If it's anchored that means we've also disabled animations. To
// reduce the chance for side effect collision, only do this if // reduce the chance for side effect collision, only do this if
@@ -2199,7 +2208,7 @@ export const TransactionTable = forwardRef((props, ref) => {
tableRef.current.unanchor(); tableRef.current.unanchor();
tableRef.current.setRowAnimation(true); tableRef.current.setRowAnimation(true);
} }
}, [prevSplitsExpanded.current]); }, [hasPrevSplitsExpanded]);
const newNavigator = useTableNavigator( const newNavigator = useTableNavigator(
newTransactions, newTransactions,
@@ -2217,14 +2226,12 @@ export const TransactionTable = forwardRef((props, ref) => {
const [_, forceRerender] = useState({}); const [_, forceRerender] = useState({});
const selectedItems = useSelectedItems(); const selectedItems = useSelectedItems();
useLayoutEffect(() => { latestState.current = {
latestState.current = { newTransactions,
newTransactions, newNavigator,
newNavigator, tableNavigator,
tableNavigator, transactions: props.transactions,
transactions: props.transactions, };
};
});
// Derive new transactions from the `isAdding` prop // Derive new transactions from the `isAdding` prop
if (prevIsAdding !== props.isAdding) { if (prevIsAdding !== props.isAdding) {
@@ -2239,32 +2246,30 @@ export const TransactionTable = forwardRef((props, ref) => {
setPrevIsAdding(props.isAdding); setPrevIsAdding(props.isAdding);
} }
useEffect(() => { if (shouldAdd.current) {
if (shouldAdd.current) { if (newTransactions[0].account == null) {
if (newTransactions[0].account == null) { dispatch(
dispatch( addNotification({
addNotification({ type: 'error',
type: 'error', message: 'Account is a required field',
message: 'Account is a required field', }),
}), );
); newNavigator.onEdit('temp', 'account');
newNavigator.onEdit('temp', 'account'); } else {
} else { const transactions = latestState.current.newTransactions;
const transactions = latestState.current.newTransactions; const lastDate = transactions.length > 0 ? transactions[0].date : null;
const lastDate = transactions.length > 0 ? transactions[0].date : null; setNewTransactions(
setNewTransactions( makeTemporaryTransactions(
makeTemporaryTransactions( props.currentAccountId,
props.currentAccountId, props.currentCategoryId,
props.currentCategoryId, lastDate,
lastDate, ),
), );
); newNavigator.onEdit('temp', 'date');
newNavigator.onEdit('temp', 'date'); props.onAdd(transactions);
props.onAdd(transactions);
}
shouldAdd.current = false;
} }
}); shouldAdd.current = false;
}
useEffect(() => { useEffect(() => {
if (savePending.current && afterSaveFunc.current) { if (savePending.current && afterSaveFunc.current) {
@@ -2273,7 +2278,7 @@ export const TransactionTable = forwardRef((props, ref) => {
} }
savePending.current = false; savePending.current = false;
}, [newTransactions, props.transactions]); }, [newTransactions, props, props.transactions]);
function getFieldsNewTransaction(item) { function getFieldsNewTransaction(item) {
const fields = [ const fields = [
@@ -2410,7 +2415,20 @@ export const TransactionTable = forwardRef((props, ref) => {
// effect we want to run. We have to wait for all updates to be // effect we want to run. We have to wait for all updates to be
// committed (the input could still be saving a value). // committed (the input could still be saving a value).
forceRerender({}); forceRerender({});
}, [props.onAdd, newNavigator.onEdit]); }, []);
const {
onSave: onSaveProp,
onApplyRules: onApplyRulesProp,
onBatchDelete,
onBatchDuplicate,
onBatchLinkSchedule,
onBatchUnlinkSchedule,
onCreateRule: onCreateRuleProp,
onScheduleAction: onScheduleActionProp,
onMakeAsNonSplitTransactions: onMakeAsNonSplitTransactionsProp,
onSplit: onSplitProp,
} = props;
const onSave = useCallback( const onSave = useCallback(
async (transaction, subtransactions = null, updatedFieldName = null) => { async (transaction, subtransactions = null, updatedFieldName = null) => {
@@ -2421,8 +2439,8 @@ export const TransactionTable = forwardRef((props, ref) => {
: transaction; : transaction;
if (isTemporaryId(transaction.id)) { if (isTemporaryId(transaction.id)) {
if (props.onApplyRules) { if (onApplyRulesProp) {
groupedTransaction = await props.onApplyRules( groupedTransaction = await onApplyRulesProp(
groupedTransaction, groupedTransaction,
updatedFieldName, updatedFieldName,
); );
@@ -2437,48 +2455,69 @@ export const TransactionTable = forwardRef((props, ref) => {
), ),
); );
} else { } else {
props.onSave(groupedTransaction); onSaveProp(groupedTransaction);
} }
}, },
[props.onSave], [onSaveProp, onApplyRulesProp],
); );
const onDelete = useCallback(id => { const onDelete = useCallback(
const temporary = isTemporaryId(id); id => {
const temporary = isTemporaryId(id);
if (temporary) { if (temporary) {
const newTrans = latestState.current.newTransactions; const newTrans = latestState.current.newTransactions;
if (id === newTrans[0].id) { if (id === newTrans[0].id) {
// You can never delete the parent new transaction // You can never delete the parent new transaction
return; return;
}
setNewTransactions(deleteTransaction(newTrans, id).data);
} else {
onBatchDelete([id]);
} }
},
[onBatchDelete],
);
setNewTransactions(deleteTransaction(newTrans, id).data); const onDuplicate = useCallback(
} else { id => {
props.onBatchDelete([id]); onBatchDuplicate([id]);
} },
}, []); [onBatchDuplicate],
);
const onDuplicate = useCallback(id => { const onLinkSchedule = useCallback(
props.onBatchDuplicate([id]); id => {
}, []); onBatchLinkSchedule([id]);
},
const onLinkSchedule = useCallback(id => { [onBatchLinkSchedule],
props.onBatchLinkSchedule([id]); );
}, []); const onUnlinkSchedule = useCallback(
const onUnlinkSchedule = useCallback(id => { id => {
props.onBatchUnlinkSchedule([id]); onBatchUnlinkSchedule([id]);
}, []); },
const onCreateRule = useCallback(id => { [onBatchUnlinkSchedule],
props.onCreateRule([id]); );
}, []); const onCreateRule = useCallback(
const onScheduleAction = useCallback((action, id) => { id => {
props.onScheduleAction(action, [id]); onCreateRuleProp([id]);
}, []); },
const onMakeAsNonSplitTransactions = useCallback(id => { [onCreateRuleProp],
props.onMakeAsNonSplitTransactions([id]); );
}, []); const onScheduleAction = useCallback(
(action, id) => {
onScheduleActionProp(action, [id]);
},
[onScheduleActionProp],
);
const onMakeAsNonSplitTransactions = useCallback(
id => {
onMakeAsNonSplitTransactionsProp([id]);
},
[onMakeAsNonSplitTransactionsProp],
);
const onSplit = useMemo(() => { const onSplit = useMemo(() => {
return id => { return id => {
@@ -2501,9 +2540,9 @@ export const TransactionTable = forwardRef((props, ref) => {
} }
} else { } else {
const trans = latestState.current.transactions.find(t => t.id === id); const trans = latestState.current.transactions.find(t => t.id === id);
const newId = props.onSplit(id); const newId = onSplitProp(id);
splitsExpanded.dispatch({ type: 'open-split', id: trans.id }); splitsExpandedDispatch({ type: 'open-split', id: trans.id });
const { tableNavigator } = latestState.current; const { tableNavigator } = latestState.current;
if (trans.amount === null) { if (trans.amount === null) {
@@ -2513,12 +2552,19 @@ export const TransactionTable = forwardRef((props, ref) => {
} }
} }
}; };
}, [props.onSplit, splitsExpanded.dispatch]); }, [onSplitProp, splitsExpandedDispatch]);
const { onAddSplit: onAddSplitProp } = props;
const onAddSplit = useCallback( const onAddSplit = useCallback(
id => { id => {
const {
tableNavigator,
newNavigator,
newTransactions: newTrans,
} = latestState.current;
if (isTemporaryId(id)) { if (isTemporaryId(id)) {
const newTrans = latestState.current.newTransactions;
const { data, diff } = addSplitTransaction(newTrans, id); const { data, diff } = addSplitTransaction(newTrans, id);
setNewTransactions(data); setNewTransactions(data);
newNavigator.onEdit( newNavigator.onEdit(
@@ -2526,19 +2572,19 @@ export const TransactionTable = forwardRef((props, ref) => {
latestState.current.newNavigator.focusedField, latestState.current.newNavigator.focusedField,
); );
} else { } else {
const newId = props.onAddSplit(id); const newId = onAddSplitProp(id);
tableNavigator.onEdit( tableNavigator.onEdit(
newId, newId,
latestState.current.tableNavigator.focusedField, latestState.current.tableNavigator.focusedField,
); );
} }
}, },
[props.onAddSplit], [onAddSplitProp],
); );
const onDistributeRemainder = useCallback( const onDistributeRemainder = useCallback(
async id => { async id => {
const { transactions, tableNavigator, newTransactions } = const { transactions, newNavigator, tableNavigator, newTransactions } =
latestState.current; latestState.current;
const targetTransactions = isTemporaryId(id) const targetTransactions = isTemporaryId(id)
@@ -2592,7 +2638,7 @@ export const TransactionTable = forwardRef((props, ref) => {
}); });
} }
}, },
[latestState], [onSave],
); );
function onCloseAddTransaction() { function onCloseAddTransaction() {
@@ -2606,8 +2652,8 @@ export const TransactionTable = forwardRef((props, ref) => {
} }
const onToggleSplit = useCallback( const onToggleSplit = useCallback(
id => splitsExpanded.dispatch({ type: 'toggle-split', id }), id => splitsExpandedDispatch({ type: 'toggle-split', id }),
[splitsExpanded.dispatch], [splitsExpandedDispatch],
); );
return ( return (

View File

@@ -130,13 +130,14 @@ type LiveTransactionTableProps = {
}; };
function LiveTransactionTable(props: LiveTransactionTableProps) { function LiveTransactionTable(props: LiveTransactionTableProps) {
const [transactions, setTransactions] = useState(props.transactions); const { transactions: transactionsProp, onTransactionsChange } = props;
const [transactions, setTransactions] = useState(transactionsProp);
useEffect(() => { useEffect(() => {
if (transactions === props.transactions) return; if (transactions === transactionsProp) return;
props.onTransactionsChange?.(transactions); onTransactionsChange?.(transactions);
// eslint-disable-next-line react-hooks/exhaustive-deps }, [transactions, transactionsProp, onTransactionsChange]);
}, [transactions]);
const onSplit = (id: string) => { const onSplit = (id: string) => {
const { data, diff } = splitTransaction(transactions, id); const { data, diff } = splitTransaction(transactions, id);

View File

@@ -0,0 +1,6 @@
---
category: Maintenance
authors: [joel-jeremy]
---
Fix react-hooks/exhaustive-deps error on TransactionsTable.jsx