|
1 | 1 | import {useEffect, useMemo, useRef} from 'react'; |
| 2 | +import {deletePendingNewTransactionIDs} from '@libs/actions/IOU/PendingNewTransactions'; |
2 | 3 | import CONST from '@src/CONST'; |
3 | 4 | import type {Transaction} from '@src/types/onyx'; |
| 5 | +import {isEmptyObject} from '@src/types/utils/EmptyObject'; |
4 | 6 | import usePrevious from './usePrevious'; |
5 | 7 |
|
6 | 8 | /** |
7 | 9 | * This hook returns new transactions that have been added since the last transactions update. |
8 | 10 | * This hook should be used only in the context of highlighting the new transactions on the Report table view. |
| 11 | + * |
| 12 | + * When `pendingNewTransactionIDs` is provided, those transactions will be treated as new even on the |
| 13 | + * first load. This handles the case where a transaction was created before the component mounts |
| 14 | + * (e.g., submitting a tracked expense from Self DM to a workspace on Web). |
9 | 15 | */ |
10 | | -function useNewTransactions(hasOnceLoadedReportActions: boolean | undefined, transactions: Transaction[] | undefined) { |
11 | | - // If we haven't loaded report yet we set previous transactions to undefined. |
| 16 | +function useNewTransactions( |
| 17 | + hasOnceLoadedReportActions: boolean | undefined, |
| 18 | + transactions: Transaction[] | undefined, |
| 19 | + pendingNewTransactionIDs?: Record<string, true | null>, |
| 20 | + reportID?: string, |
| 21 | + isFocused?: boolean, |
| 22 | +) { |
| 23 | + // If we haven't loaded report yet we set previous transaction ids to undefined. |
12 | 24 | const prevTransactions = usePrevious(hasOnceLoadedReportActions ? transactions : undefined); |
13 | 25 |
|
14 | 26 | // We need to skip the first transactions change, to avoid highlighting transactions on the first load. |
15 | 27 | const skipFirstTransactionsChange = useRef(!hasOnceLoadedReportActions); |
16 | 28 |
|
17 | 29 | const newTransactions = useMemo(() => { |
| 30 | + // If isFocused is not passed (=undefined) we will not return empty. |
| 31 | + if (isFocused === false) { |
| 32 | + return CONST.EMPTY_ARRAY as unknown as Transaction[]; |
| 33 | + } |
| 34 | + |
18 | 35 | if (transactions === undefined || prevTransactions === undefined || transactions.length <= prevTransactions.length) { |
| 36 | + // When a transaction is submitted from another report (e.g., Self DM → workspace), it is |
| 37 | + // already in the transactions list by the time this component mounts. |
| 38 | + // So we use pendingNewTransactionIDs from report metadata to identify these transactions on first load. |
| 39 | + if (isFocused && reportID && !isEmptyObject(pendingNewTransactionIDs) && transactions?.length) { |
| 40 | + const pendingSet = new Set(Object.keys(pendingNewTransactionIDs)); |
| 41 | + const pendingTransactions = transactions.filter(({transactionID}) => pendingSet.has(transactionID) && pendingNewTransactionIDs[transactionID]); |
| 42 | + return pendingTransactions; |
| 43 | + } |
19 | 44 | return CONST.EMPTY_ARRAY as unknown as Transaction[]; |
20 | 45 | } |
21 | 46 | if (skipFirstTransactionsChange.current) { |
22 | 47 | skipFirstTransactionsChange.current = false; |
23 | 48 | return CONST.EMPTY_ARRAY as unknown as Transaction[]; |
24 | 49 | } |
25 | 50 | return transactions.filter((transaction) => !prevTransactions?.some((prevTransaction) => prevTransaction.transactionID === transaction.transactionID)); |
26 | | - }, [transactions, prevTransactions]); |
| 51 | + |
| 52 | + // We don't need to recalculate on change of prevTransactions or pendingNewTransactionIDs as it will make the value |
| 53 | + // disappear quickly which will break the scroll and highlight on slower devices like mobile app. |
| 54 | + // eslint-disable-next-line react-hooks/exhaustive-deps |
| 55 | + }, [transactions, reportID, isFocused]); |
| 56 | + |
| 57 | + useEffect(() => { |
| 58 | + if (!pendingNewTransactionIDs) { |
| 59 | + return; |
| 60 | + } |
| 61 | + const pendingSet = new Set(Object.keys(pendingNewTransactionIDs)); |
| 62 | + const pendingTransactions = newTransactions.filter(({transactionID}) => pendingSet.has(transactionID) && pendingNewTransactionIDs[transactionID]); |
| 63 | + if (!pendingTransactions.length) { |
| 64 | + return; |
| 65 | + } |
| 66 | + |
| 67 | + // We deletePendingNewTransactionIDs after the scroll and highlight has occurred. |
| 68 | + setTimeout(() => { |
| 69 | + deletePendingNewTransactionIDs( |
| 70 | + reportID, |
| 71 | + pendingTransactions.map((transaction) => transaction.transactionID), |
| 72 | + ); |
| 73 | + }, CONST.PENDING_TRANSACTION_DELETION_DELAY); |
| 74 | + }, [pendingNewTransactionIDs, newTransactions, reportID]); |
27 | 75 |
|
28 | 76 | // In case when we have loaded the report, but there were no transactions in it, then we need to explicitly set skipFirstTransactionsChange to false, as it will be not set in the useMemo above. |
29 | 77 | useEffect(() => { |
|
0 commit comments