diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportView.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportView.tsx index 25564056e75e..9309c30a94cb 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportView.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportView.tsx @@ -10,8 +10,8 @@ import OfflineWithFeedback from '@components/OfflineWithFeedback'; import ReportActionsSkeletonView from '@components/ReportActionsSkeletonView'; import ReportHeaderSkeletonView from '@components/ReportHeaderSkeletonView'; import useNetwork from '@hooks/useNetwork'; +import useNewTransactions from '@hooks/useNewTransactions'; import usePaginatedReportActions from '@hooks/usePaginatedReportActions'; -import usePrevious from '@hooks/usePrevious'; import useThemeStyles from '@hooks/useThemeStyles'; import {removeFailedReport} from '@libs/actions/Report'; import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID'; @@ -24,7 +24,6 @@ import {buildCannedSearchQuery} from '@libs/SearchQueryUtils'; import Navigation from '@navigation/Navigation'; import ReportActionsView from '@pages/home/report/ReportActionsView'; import ReportFooter from '@pages/home/report/ReportFooter'; -import CONST from '@src/CONST'; import NAVIGATORS from '@src/NAVIGATORS'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Route} from '@src/ROUTES'; @@ -106,17 +105,7 @@ function MoneyRequestReportView({report, policy, reportMetadata, shouldDisplayRe const reportTransactionIDs = transactions?.map((transaction) => transaction.transactionID); const transactionThreadReportID = getOneTransactionThreadReportID(report, chatReport, reportActions ?? [], isOffline, reportTransactionIDs); - const prevTransactions = usePrevious(transactions); - - const newTransactions = useMemo(() => { - if (!prevTransactions || !transactions || transactions.length <= prevTransactions.length) { - return CONST.EMPTY_ARRAY as unknown as OnyxTypes.Transaction[]; - } - return transactions.filter((transaction) => !prevTransactions?.some((prevTransaction) => prevTransaction.transactionID === transaction.transactionID)); - // Depending only on transactions is enough because prevTransactions is a helper object. - // eslint-disable-next-line react-compiler/react-compiler - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [transactions]); + const newTransactions = useNewTransactions(reportMetadata?.hasOnceLoadedReportActions, transactions); const [parentReportAction] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${getNonEmptyStringOnyxID(report?.parentReportID)}`, { canEvict: false, diff --git a/src/hooks/useNewTransactions.ts b/src/hooks/useNewTransactions.ts new file mode 100644 index 000000000000..e79ae1925b2c --- /dev/null +++ b/src/hooks/useNewTransactions.ts @@ -0,0 +1,49 @@ +import {useEffect, useMemo, useRef} from 'react'; +import CONST from '@src/CONST'; +import type {Transaction} from '@src/types/onyx'; +import usePrevious from './usePrevious'; + +/** + * This hook returns new transactions that have been added since the last transactions update. + * This hook should be used only in the context of highlighting the new transactions on the Report table view. + */ +function useNewTransactions(hasOnceLoadedReportActions: boolean | undefined, transactions: Transaction[] | undefined) { + // If we haven't loaded report yet we set previous transactions to undefined. + const prevTransactions = usePrevious(hasOnceLoadedReportActions ? transactions : undefined); + + // We need to skip the first transactions change, to avoid highlighting transactions on the first load. + const skipFirstTransactionsChange = useRef(!hasOnceLoadedReportActions); + + const newTransactions = useMemo(() => { + if (transactions === undefined || prevTransactions === undefined || transactions.length <= prevTransactions.length) { + return CONST.EMPTY_ARRAY as unknown as Transaction[]; + } + if (skipFirstTransactionsChange.current) { + skipFirstTransactionsChange.current = false; + return CONST.EMPTY_ARRAY as unknown as Transaction[]; + } + return transactions.filter((transaction) => !prevTransactions?.some((prevTransaction) => prevTransaction.transactionID === transaction.transactionID)); + // Depending only on transactions is enough because prevTransactions is a helper object. + // eslint-disable-next-line react-compiler/react-compiler + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [transactions]); + + // 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. + useEffect(() => { + if (!hasOnceLoadedReportActions) { + return; + } + // This is needed to ensure that set we skipFirstTransactionsChange to false only after the Onyx merge is done. + new Promise((resolve) => { + resolve(); + }).then(() => { + requestAnimationFrame(() => { + skipFirstTransactionsChange.current = false; + }); + }); + }, [hasOnceLoadedReportActions]); + + return newTransactions; +} + +export default useNewTransactions; diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index e7d66ba216ac..ac1d984838bf 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -21,6 +21,7 @@ import useDeepCompareRef from '@hooks/useDeepCompareRef'; import useIsReportReadyToDisplay from '@hooks/useIsReportReadyToDisplay'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; +import useNewTransactions from '@hooks/useNewTransactions'; import useOnyx from '@hooks/useOnyx'; import usePaginatedReportActions from '@hooks/usePaginatedReportActions'; import usePermissions from '@hooks/usePermissions'; @@ -314,17 +315,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { // We need to wait for both the selector to finish AND ensure we're not in a loading state where transactions could still populate const shouldWaitForTransactions = shouldWaitForTransactionsUtil(report, reportTransactions, reportMetadata); - const prevTransactions = usePrevious(reportTransactions); - - const newTransactions = useMemo(() => { - if (!reportTransactions || !prevTransactions || reportTransactions.length <= prevTransactions.length) { - return CONST.EMPTY_ARRAY as unknown as OnyxTypes.Transaction[]; - } - return reportTransactions.filter((transaction) => !prevTransactions?.some((prevTransaction) => prevTransaction.transactionID === transaction.transactionID)); - // Depending only on transactions is enough because prevTransactions is a helper object. - // eslint-disable-next-line react-compiler/react-compiler - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [reportTransactions]); + const newTransactions = useNewTransactions(reportMetadata?.hasOnceLoadedReportActions, reportTransactions); useEffect(() => { if (!prevIsFocused || isFocused) {