From 3188cc9186e4f11d30fed8381c916a7808203e19 Mon Sep 17 00:00:00 2001 From: Jakub Szymczak Date: Thu, 12 Jun 2025 15:16:32 +0200 Subject: [PATCH 1/8] stop highlighting old transaction, when cache was cleared --- src/CONST.ts | 1 + .../MoneyRequestReportView.tsx | 18 ++++++++++++++++-- src/pages/home/ReportScreen.tsx | 16 +++++++++++++++- 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 97528c8659bf..8463f33fe912 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1687,6 +1687,7 @@ const CONST = { PUSHER_PING_PONG: 'pusher_ping_pong', LOCATION_UPDATE_INTERVAL: 5000, PLAY_SOUND_MESSAGE_DEBOUNCE_TIME: 500, + UPDATE_PREV_TRANSACTION_TIMEOUT: 2000, SKELETON_ANIMATION_SPEED: 3, SEARCH_OPTIONS_COMPARISON: 'search_options_comparison', }, diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportView.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportView.tsx index fb1c265787c5..b2a3bb2022c3 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportView.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportView.tsx @@ -1,5 +1,5 @@ import {PortalHost} from '@gorhom/portal'; -import React, {useCallback, useMemo} from 'react'; +import React, {useCallback, useEffect, useMemo, useRef} from 'react'; import {InteractionManager, View} from 'react-native'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import {useOnyx} from 'react-native-onyx'; @@ -97,6 +97,15 @@ function MoneyRequestReportView({report, policy, reportMetadata, shouldDisplayRe const {reportActions: unfilteredReportActions, hasNewerActions, hasOlderActions} = usePaginatedReportActions(reportID); const reportActions = getFilteredReportActionsForReportView(unfilteredReportActions); + // ref used to not compute new transaction on the first full load to avoid highlighting transactions that belonged to the report but weren't present in Onyx + const firstFullLoadTime = useRef(undefined); + useEffect(() => { + if (!reportMetadata?.hasOnceLoadedReportActions || firstFullLoadTime.current) { + return; + } + firstFullLoadTime.current = Date.now(); + }, [reportMetadata?.hasOnceLoadedReportActions]); + const [transactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, { selector: (allTransactions: OnyxCollection) => selectAllTransactionsForReport(allTransactions, reportID, reportActions), canBeMissing: true, @@ -108,7 +117,12 @@ function MoneyRequestReportView({report, policy, reportMetadata, shouldDisplayRe const prevTransactions = usePrevious(transactions); const newTransactions = useMemo(() => { - if (!prevTransactions || !transactions || transactions.length <= prevTransactions.length) { + if ( + transactions === undefined || + prevTransactions === undefined || + transactions.length <= prevTransactions.length || + (firstFullLoadTime.current && firstFullLoadTime.current + CONST.TIMING.UPDATE_PREV_TRANSACTION_TIMEOUT > Date.now()) + ) { return CONST.EMPTY_ARRAY as unknown as OnyxTypes.Transaction[]; } return transactions.filter((transaction) => !prevTransactions?.some((prevTransaction) => prevTransaction.transactionID === transaction.transactionID)); diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 7d64ded33e7f..809b22f62085 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -294,6 +294,8 @@ function ReportScreen({route, navigation}: ReportScreenProps) { // OpenReport will be called each time the user scrolls up the report a bit, clicks on report preview, and then goes back. const isLinkedMessagePageReady = isLinkedMessageAvailable && (reportActions.length - indexOfLinkedMessage >= CONST.REPORT.MIN_INITIAL_REPORT_ACTION_COUNT || doesCreatedActionExists()); + // ref used to not compute new transaction on the first full load to avoid highlighting transactions that belonged to the report but weren't present in Onyx + const firstFullLoadTime = useRef(undefined); const [reportTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, { selector: (allTransactions): OnyxTypes.Transaction[] => selectAllTransactionsForReport(allTransactions, reportIDFromRoute, reportActions), canBeMissing: false, @@ -315,7 +317,12 @@ function ReportScreen({route, navigation}: ReportScreenProps) { const prevTransactions = usePrevious(reportTransactions); const newTransactions = useMemo(() => { - if (!reportTransactions || !prevTransactions || reportTransactions.length <= prevTransactions.length) { + if ( + reportTransactions === undefined || + prevTransactions === undefined || + reportTransactions.length <= prevTransactions.length || + (firstFullLoadTime.current && firstFullLoadTime.current + CONST.TIMING.UPDATE_PREV_TRANSACTION_TIMEOUT > Date.now()) + ) { return CONST.EMPTY_ARRAY as unknown as OnyxTypes.Transaction[]; } return reportTransactions.filter((transaction) => !prevTransactions?.some((prevTransaction) => prevTransaction.transactionID === transaction.transactionID)); @@ -755,6 +762,13 @@ function ReportScreen({route, navigation}: ReportScreenProps) { readNewestAction(report?.reportID); }, [report]); + useEffect(() => { + if (!reportMetadata?.hasOnceLoadedReportActions || firstFullLoadTime.current) { + return; + } + firstFullLoadTime.current = Date.now(); + }, [reportMetadata?.hasOnceLoadedReportActions]); + const lastRoute = usePrevious(route); const onComposerFocus = useCallback(() => setIsComposerFocus(true), []); From ba3cab345f869afd32a854cd2e736eeef03e8e31 Mon Sep 17 00:00:00 2001 From: Jakub Szymczak Date: Thu, 12 Jun 2025 15:25:09 +0200 Subject: [PATCH 2/8] add hasOnceLoadedReportActions condition to init firstFullLoadTime ref --- .../MoneyRequestReportView/MoneyRequestReportView.tsx | 2 +- src/pages/home/ReportScreen.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportView.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportView.tsx index b2a3bb2022c3..17e72a584abd 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportView.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportView.tsx @@ -98,7 +98,7 @@ function MoneyRequestReportView({report, policy, reportMetadata, shouldDisplayRe const reportActions = getFilteredReportActionsForReportView(unfilteredReportActions); // ref used to not compute new transaction on the first full load to avoid highlighting transactions that belonged to the report but weren't present in Onyx - const firstFullLoadTime = useRef(undefined); + const firstFullLoadTime = useRef(reportMetadata?.hasOnceLoadedReportActions ? Date.now() : undefined); useEffect(() => { if (!reportMetadata?.hasOnceLoadedReportActions || firstFullLoadTime.current) { return; diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 809b22f62085..114a181efedf 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -295,7 +295,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { const isLinkedMessagePageReady = isLinkedMessageAvailable && (reportActions.length - indexOfLinkedMessage >= CONST.REPORT.MIN_INITIAL_REPORT_ACTION_COUNT || doesCreatedActionExists()); // ref used to not compute new transaction on the first full load to avoid highlighting transactions that belonged to the report but weren't present in Onyx - const firstFullLoadTime = useRef(undefined); + const firstFullLoadTime = useRef(reportMetadata?.hasOnceLoadedReportActions ? Date.now() : undefined); const [reportTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, { selector: (allTransactions): OnyxTypes.Transaction[] => selectAllTransactionsForReport(allTransactions, reportIDFromRoute, reportActions), canBeMissing: false, From eb85de7455ff3799d7824c4bceee99569caba357 Mon Sep 17 00:00:00 2001 From: Jakub Szymczak Date: Fri, 13 Jun 2025 16:21:27 +0200 Subject: [PATCH 3/8] refactor new transaction highlightitng logic --- .../MoneyRequestReportView.tsx | 33 +++++++++--------- src/libs/MoneyRequestReportUtils.ts | 2 +- src/pages/home/ReportScreen.tsx | 34 +++++++++---------- 3 files changed, 34 insertions(+), 35 deletions(-) diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportView.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportView.tsx index 17e72a584abd..5546e116cba6 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportView.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportView.tsx @@ -97,15 +97,6 @@ function MoneyRequestReportView({report, policy, reportMetadata, shouldDisplayRe const {reportActions: unfilteredReportActions, hasNewerActions, hasOlderActions} = usePaginatedReportActions(reportID); const reportActions = getFilteredReportActionsForReportView(unfilteredReportActions); - // ref used to not compute new transaction on the first full load to avoid highlighting transactions that belonged to the report but weren't present in Onyx - const firstFullLoadTime = useRef(reportMetadata?.hasOnceLoadedReportActions ? Date.now() : undefined); - useEffect(() => { - if (!reportMetadata?.hasOnceLoadedReportActions || firstFullLoadTime.current) { - return; - } - firstFullLoadTime.current = Date.now(); - }, [reportMetadata?.hasOnceLoadedReportActions]); - const [transactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, { selector: (allTransactions: OnyxCollection) => selectAllTransactionsForReport(allTransactions, reportID, reportActions), canBeMissing: true, @@ -114,15 +105,14 @@ function MoneyRequestReportView({report, policy, reportMetadata, shouldDisplayRe const reportTransactionIDs = transactions?.map((transaction) => transaction.transactionID); const transactionThreadReportID = getOneTransactionThreadReportID(reportID, reportActions ?? [], isOffline, reportTransactionIDs); - const prevTransactions = usePrevious(transactions); - + const prevTransactions = usePrevious(reportMetadata?.hasOnceLoadedReportActions ? transactions : undefined); + const skipFirstTransactionsChange = useRef(!!reportMetadata?.hasOnceLoadedReportActions); const newTransactions = useMemo(() => { - if ( - transactions === undefined || - prevTransactions === undefined || - transactions.length <= prevTransactions.length || - (firstFullLoadTime.current && firstFullLoadTime.current + CONST.TIMING.UPDATE_PREV_TRANSACTION_TIMEOUT > Date.now()) - ) { + if (transactions === undefined || prevTransactions === undefined || transactions.length <= prevTransactions.length) { + return CONST.EMPTY_ARRAY as unknown as OnyxTypes.Transaction[]; + } + if (!skipFirstTransactionsChange.current) { + skipFirstTransactionsChange.current = true; return CONST.EMPTY_ARRAY as unknown as OnyxTypes.Transaction[]; } return transactions.filter((transaction) => !prevTransactions?.some((prevTransaction) => prevTransaction.transactionID === transaction.transactionID)); @@ -131,6 +121,15 @@ function MoneyRequestReportView({report, policy, reportMetadata, shouldDisplayRe // eslint-disable-next-line react-hooks/exhaustive-deps }, [transactions]); + useEffect(() => { + if (!reportMetadata?.hasOnceLoadedReportActions) { + return; + } + Navigation.setNavigationActionToMicrotaskQueue(() => { + skipFirstTransactionsChange.current = true; + }); + }, [reportMetadata?.hasOnceLoadedReportActions]); + const [parentReportAction] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${getNonEmptyStringOnyxID(report?.parentReportID)}`, { canEvict: false, canBeMissing: true, diff --git a/src/libs/MoneyRequestReportUtils.ts b/src/libs/MoneyRequestReportUtils.ts index c027f055593e..c946eb20bd98 100644 --- a/src/libs/MoneyRequestReportUtils.ts +++ b/src/libs/MoneyRequestReportUtils.ts @@ -57,7 +57,7 @@ function getThreadReportIDsForTransactions(reportActions: ReportAction[], transa */ function selectAllTransactionsForReport(transactions: OnyxCollection, reportID: string | undefined, reportActions: ReportAction[]) { if (!reportID) { - return []; + return undefined; } return Object.values(transactions ?? {}).filter((transaction): transaction is Transaction => { diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 114a181efedf..89b99d7c513d 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -294,10 +294,8 @@ function ReportScreen({route, navigation}: ReportScreenProps) { // OpenReport will be called each time the user scrolls up the report a bit, clicks on report preview, and then goes back. const isLinkedMessagePageReady = isLinkedMessageAvailable && (reportActions.length - indexOfLinkedMessage >= CONST.REPORT.MIN_INITIAL_REPORT_ACTION_COUNT || doesCreatedActionExists()); - // ref used to not compute new transaction on the first full load to avoid highlighting transactions that belonged to the report but weren't present in Onyx - const firstFullLoadTime = useRef(reportMetadata?.hasOnceLoadedReportActions ? Date.now() : undefined); const [reportTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, { - selector: (allTransactions): OnyxTypes.Transaction[] => selectAllTransactionsForReport(allTransactions, reportIDFromRoute, reportActions), + selector: (allTransactions): OnyxTypes.Transaction[] | undefined => selectAllTransactionsForReport(allTransactions, reportIDFromRoute, reportActions), canBeMissing: false, }); const reportTransactionIDs = reportTransactions?.map((transaction) => transaction.transactionID); @@ -314,15 +312,15 @@ 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 prevTransactions = usePrevious(reportMetadata?.hasOnceLoadedReportActions ? reportTransactions : undefined); + const skipFirstTransactionsChange = useRef(!!reportMetadata?.hasOnceLoadedReportActions); const newTransactions = useMemo(() => { - if ( - reportTransactions === undefined || - prevTransactions === undefined || - reportTransactions.length <= prevTransactions.length || - (firstFullLoadTime.current && firstFullLoadTime.current + CONST.TIMING.UPDATE_PREV_TRANSACTION_TIMEOUT > Date.now()) - ) { + if (reportTransactions === undefined || prevTransactions === undefined || reportTransactions.length <= prevTransactions.length) { + return CONST.EMPTY_ARRAY as unknown as OnyxTypes.Transaction[]; + } + if (!skipFirstTransactionsChange.current) { + skipFirstTransactionsChange.current = true; return CONST.EMPTY_ARRAY as unknown as OnyxTypes.Transaction[]; } return reportTransactions.filter((transaction) => !prevTransactions?.some((prevTransaction) => prevTransaction.transactionID === transaction.transactionID)); @@ -331,6 +329,15 @@ function ReportScreen({route, navigation}: ReportScreenProps) { // eslint-disable-next-line react-hooks/exhaustive-deps }, [reportTransactions]); + useEffect(() => { + if (!reportMetadata?.hasOnceLoadedReportActions) { + return; + } + Navigation.setNavigationActionToMicrotaskQueue(() => { + skipFirstTransactionsChange.current = true; + }); + }, [reportMetadata?.hasOnceLoadedReportActions]); + useEffect(() => { if (!prevIsFocused || isFocused) { return; @@ -762,13 +769,6 @@ function ReportScreen({route, navigation}: ReportScreenProps) { readNewestAction(report?.reportID); }, [report]); - useEffect(() => { - if (!reportMetadata?.hasOnceLoadedReportActions || firstFullLoadTime.current) { - return; - } - firstFullLoadTime.current = Date.now(); - }, [reportMetadata?.hasOnceLoadedReportActions]); - const lastRoute = usePrevious(route); const onComposerFocus = useCallback(() => setIsComposerFocus(true), []); From 889634fdd2be8497c15e7ea16e34881831d809e6 Mon Sep 17 00:00:00 2001 From: Jakub Szymczak Date: Mon, 16 Jun 2025 11:43:12 +0200 Subject: [PATCH 4/8] clean up transaction highlighting logic and add explanation comments --- .../MoneyRequestReportView.tsx | 30 ++---------- src/hooks/useNewTransactions.ts | 49 +++++++++++++++++++ src/pages/home/ReportScreen.tsx | 27 +--------- 3 files changed, 54 insertions(+), 52 deletions(-) create mode 100644 src/hooks/useNewTransactions.ts diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportView.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportView.tsx index 5546e116cba6..e4e482838f54 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportView.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportView.tsx @@ -1,5 +1,5 @@ import {PortalHost} from '@gorhom/portal'; -import React, {useCallback, useEffect, useMemo, useRef} from 'react'; +import React, {useCallback, useMemo} from 'react'; import {InteractionManager, View} from 'react-native'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import {useOnyx} from 'react-native-onyx'; @@ -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'; @@ -105,30 +104,7 @@ function MoneyRequestReportView({report, policy, reportMetadata, shouldDisplayRe const reportTransactionIDs = transactions?.map((transaction) => transaction.transactionID); const transactionThreadReportID = getOneTransactionThreadReportID(reportID, reportActions ?? [], isOffline, reportTransactionIDs); - const prevTransactions = usePrevious(reportMetadata?.hasOnceLoadedReportActions ? transactions : undefined); - const skipFirstTransactionsChange = useRef(!!reportMetadata?.hasOnceLoadedReportActions); - const newTransactions = useMemo(() => { - if (transactions === undefined || prevTransactions === undefined || transactions.length <= prevTransactions.length) { - return CONST.EMPTY_ARRAY as unknown as OnyxTypes.Transaction[]; - } - if (!skipFirstTransactionsChange.current) { - skipFirstTransactionsChange.current = true; - 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]); - - useEffect(() => { - if (!reportMetadata?.hasOnceLoadedReportActions) { - return; - } - Navigation.setNavigationActionToMicrotaskQueue(() => { - skipFirstTransactionsChange.current = true; - }); - }, [reportMetadata?.hasOnceLoadedReportActions]); + 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..f69ba643178c --- /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, if we load the report for the first time, + // to avoid highlighting transactions that were already present in the report, but weren't saved in Onyx yet. + 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 the Onyx merge is done before we set skipFirstTransactionsChange to false. + 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 89b99d7c513d..6eb0a6b0b446 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'; @@ -312,31 +313,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(reportMetadata?.hasOnceLoadedReportActions ? reportTransactions : undefined); - - const skipFirstTransactionsChange = useRef(!!reportMetadata?.hasOnceLoadedReportActions); - const newTransactions = useMemo(() => { - if (reportTransactions === undefined || prevTransactions === undefined || reportTransactions.length <= prevTransactions.length) { - return CONST.EMPTY_ARRAY as unknown as OnyxTypes.Transaction[]; - } - if (!skipFirstTransactionsChange.current) { - skipFirstTransactionsChange.current = true; - 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]); - - useEffect(() => { - if (!reportMetadata?.hasOnceLoadedReportActions) { - return; - } - Navigation.setNavigationActionToMicrotaskQueue(() => { - skipFirstTransactionsChange.current = true; - }); - }, [reportMetadata?.hasOnceLoadedReportActions]); + const newTransactions = useNewTransactions(reportMetadata?.hasOnceLoadedReportActions, reportTransactions); useEffect(() => { if (!prevIsFocused || isFocused) { From 27f66a10bf3077f32c09177ed036c89b00de52b2 Mon Sep 17 00:00:00 2001 From: Jakub Szymczak Date: Mon, 16 Jun 2025 11:47:01 +0200 Subject: [PATCH 5/8] remove unused code --- src/CONST.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/CONST.ts b/src/CONST.ts index 8463f33fe912..97528c8659bf 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1687,7 +1687,6 @@ const CONST = { PUSHER_PING_PONG: 'pusher_ping_pong', LOCATION_UPDATE_INTERVAL: 5000, PLAY_SOUND_MESSAGE_DEBOUNCE_TIME: 500, - UPDATE_PREV_TRANSACTION_TIMEOUT: 2000, SKELETON_ANIMATION_SPEED: 3, SEARCH_OPTIONS_COMPARISON: 'search_options_comparison', }, From 82f7bd3192f8ddd60fca69fd5b441b88cc9e12f3 Mon Sep 17 00:00:00 2001 From: Jakub Szymczak Date: Mon, 16 Jun 2025 13:03:49 +0200 Subject: [PATCH 6/8] fix PR comments --- src/hooks/useNewTransactions.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/hooks/useNewTransactions.ts b/src/hooks/useNewTransactions.ts index f69ba643178c..e79ae1925b2c 100644 --- a/src/hooks/useNewTransactions.ts +++ b/src/hooks/useNewTransactions.ts @@ -11,9 +11,9 @@ function useNewTransactions(hasOnceLoadedReportActions: boolean | undefined, tra // 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, if we load the report for the first time, - // to avoid highlighting transactions that were already present in the report, but weren't saved in Onyx yet. - const skipFirstTransactionsChange = useRef(!hasOnceLoadedReportActions); + // 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[]; @@ -33,7 +33,7 @@ function useNewTransactions(hasOnceLoadedReportActions: boolean | undefined, tra if (!hasOnceLoadedReportActions) { return; } - // This is needed to ensure that the Onyx merge is done before we set skipFirstTransactionsChange to false. + // This is needed to ensure that set we skipFirstTransactionsChange to false only after the Onyx merge is done. new Promise((resolve) => { resolve(); }).then(() => { From f5c10f4fd7c6f785c730d239c676f4d192b33feb Mon Sep 17 00:00:00 2001 From: Jakub Szymczak Date: Mon, 16 Jun 2025 13:14:44 +0200 Subject: [PATCH 7/8] fix typescript --- src/pages/home/report/ReportActionsView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionsView.tsx b/src/pages/home/report/ReportActionsView.tsx index c0679a28f2b1..ab25c4d4e37e 100755 --- a/src/pages/home/report/ReportActionsView.tsx +++ b/src/pages/home/report/ReportActionsView.tsx @@ -97,7 +97,7 @@ function ReportActionsView({ const isReportFullyVisible = useMemo((): boolean => getIsReportFullyVisible(isFocused), [isFocused]); const [reportTransactionIDs] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, { selector: (allTransactions: OnyxCollection) => - selectAllTransactionsForReport(allTransactions, reportID, allReportActions ?? []).map((transaction) => transaction.transactionID), + selectAllTransactionsForReport(allTransactions, reportID, allReportActions ?? [])?.map((transaction) => transaction.transactionID), canBeMissing: true, }); From 70e75947e4f262776280b77375ed8be66e515eed Mon Sep 17 00:00:00 2001 From: Jakub Szymczak Date: Tue, 17 Jun 2025 09:31:20 +0200 Subject: [PATCH 8/8] come back to returning empty arrray in selectAllTransactionsForReport selector --- src/libs/MoneyRequestReportUtils.ts | 2 +- src/pages/home/ReportScreen.tsx | 2 +- src/pages/home/report/ReportActionsView.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/MoneyRequestReportUtils.ts b/src/libs/MoneyRequestReportUtils.ts index c946eb20bd98..c027f055593e 100644 --- a/src/libs/MoneyRequestReportUtils.ts +++ b/src/libs/MoneyRequestReportUtils.ts @@ -57,7 +57,7 @@ function getThreadReportIDsForTransactions(reportActions: ReportAction[], transa */ function selectAllTransactionsForReport(transactions: OnyxCollection, reportID: string | undefined, reportActions: ReportAction[]) { if (!reportID) { - return undefined; + return []; } return Object.values(transactions ?? {}).filter((transaction): transaction is Transaction => { diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 7d39f7546d82..ac1d984838bf 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -298,7 +298,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { const isLinkedMessagePageReady = isLinkedMessageAvailable && (reportActions.length - indexOfLinkedMessage >= CONST.REPORT.MIN_INITIAL_REPORT_ACTION_COUNT || doesCreatedActionExists()); const [reportTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, { - selector: (allTransactions): OnyxTypes.Transaction[] | undefined => selectAllTransactionsForReport(allTransactions, reportIDFromRoute, reportActions), + selector: (allTransactions): OnyxTypes.Transaction[] => selectAllTransactionsForReport(allTransactions, reportIDFromRoute, reportActions), canBeMissing: false, }); const reportTransactionIDs = reportTransactions?.map((transaction) => transaction.transactionID); diff --git a/src/pages/home/report/ReportActionsView.tsx b/src/pages/home/report/ReportActionsView.tsx index ab25c4d4e37e..c0679a28f2b1 100755 --- a/src/pages/home/report/ReportActionsView.tsx +++ b/src/pages/home/report/ReportActionsView.tsx @@ -97,7 +97,7 @@ function ReportActionsView({ const isReportFullyVisible = useMemo((): boolean => getIsReportFullyVisible(isFocused), [isFocused]); const [reportTransactionIDs] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, { selector: (allTransactions: OnyxCollection) => - selectAllTransactionsForReport(allTransactions, reportID, allReportActions ?? [])?.map((transaction) => transaction.transactionID), + selectAllTransactionsForReport(allTransactions, reportID, allReportActions ?? []).map((transaction) => transaction.transactionID), canBeMissing: true, });