Skip to content

Commit 21b0eb8

Browse files
authored
Merge pull request Expensify#83932 from margelo/@chrispader/allow-multiple-report-action-loads
fix: Allow multiple loads in useLoadReportActions hook (AFTER REVERT)
2 parents 93a65ac + d9a4c18 commit 21b0eb8

10 files changed

Lines changed: 183 additions & 150 deletions

File tree

src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import Checkbox from '@components/Checkbox';
1313
import DecisionModal from '@components/DecisionModal';
1414
import {useDelegateNoAccessActions, useDelegateNoAccessState} from '@components/DelegateNoAccessModalProvider';
1515
import FlatListWithScrollKey from '@components/FlatList/FlatListWithScrollKey';
16-
import {AUTOSCROLL_TO_TOP_THRESHOLD} from '@components/FlatList/hooks/useFlatListScrollKey';
1716
import HoldOrRejectEducationalModal from '@components/HoldOrRejectEducationalModal';
1817
import {ModalActions} from '@components/Modal/Global/ModalContext';
1918
import OfflineWithFeedback from '@components/OfflineWithFeedback';
@@ -35,6 +34,7 @@ import usePrevious from '@hooks/usePrevious';
3534
import useReportIsArchived from '@hooks/useReportIsArchived';
3635
import useReportScrollManager from '@hooks/useReportScrollManager';
3736
import useResponsiveLayoutOnWideRHP from '@hooks/useResponsiveLayoutOnWideRHP';
37+
import useScrollToEndOnNewMessageReceived from '@hooks/useScrollToEndOnNewMessageReceived';
3838
import useSelectedTransactionsActions from '@hooks/useSelectedTransactionsActions';
3939
import useThemeStyles from '@hooks/useThemeStyles';
4040
import useWindowDimensions from '@hooks/useWindowDimensions';
@@ -371,10 +371,7 @@ function MoneyRequestReportActionsList({
371371
markOpenReportEnd(report, {warm: false});
372372
}, [report, shouldShowOpenReportLoadingSkeleton]);
373373

374-
const reportActionSize = useRef(visibleReportActions.length);
375374
const lastAction = visibleReportActions.at(-1);
376-
const lastActionIndex = lastAction?.reportActionID;
377-
const previousLastIndex = useRef(lastActionIndex);
378375

379376
const {scrollOffsetRef} = useContext(ActionListContext);
380377
const scrollingVerticalBottomOffset = useRef(0);
@@ -383,7 +380,6 @@ function MoneyRequestReportActionsList({
383380
const readActionSkipped = useRef(false);
384381
const lastVisibleActionCreated = getReportLastVisibleActionCreated(report, transactionThreadReport);
385382
const hasNewestReportAction = lastAction?.created === lastVisibleActionCreated;
386-
const hasNewestReportActionRef = useRef(hasNewestReportAction);
387383
const userActiveSince = useRef<string>(DateUtils.getDBTime());
388384

389385
const reportActionIDs = useMemo(() => {
@@ -576,21 +572,16 @@ function MoneyRequestReportActionsList({
576572
hasOnceLoadedReportActions: !!reportMetadata?.hasOnceLoadedReportActions,
577573
});
578574

579-
useEffect(() => {
580-
if (
581-
scrollingVerticalBottomOffset.current < AUTOSCROLL_TO_TOP_THRESHOLD &&
582-
previousLastIndex.current !== lastActionIndex &&
583-
reportActionSize.current > reportActions.length &&
584-
hasNewestReportAction
585-
) {
586-
setIsFloatingMessageCounterVisible(false);
587-
reportScrollManager.scrollToEnd();
588-
}
589-
590-
previousLastIndex.current = lastActionIndex;
591-
reportActionSize.current = visibleReportActions.length;
592-
hasNewestReportActionRef.current = hasNewestReportAction;
593-
}, [lastActionIndex, reportActions.length, reportScrollManager, hasNewestReportAction, visibleReportActions.length, setIsFloatingMessageCounterVisible]);
575+
useScrollToEndOnNewMessageReceived({
576+
sizeChangeType: 'grewFromReportActions',
577+
scrollOffsetRef,
578+
lastActionID: lastAction?.reportActionID,
579+
visibleActionsLength: visibleReportActions.length,
580+
reportActionsLength: reportActions.length,
581+
hasNewestReportAction,
582+
setIsFloatingMessageCounterVisible,
583+
scrollToEnd: reportScrollManager.scrollToEnd,
584+
});
594585

595586
/**
596587
* Subscribe to read/unread events and update our unreadMarkerTime
@@ -721,6 +712,7 @@ function MoneyRequestReportActionsList({
721712
reportActionsObject,
722713
parentReportAction,
723714
report,
715+
isOffline,
724716
transactionThreadReport,
725717
mostRecentIOUReportActionID,
726718
unreadMarkerReportActionID,

src/hooks/useLoadReportActions.ts

Lines changed: 67 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import {useIsFocused} from '@react-navigation/native';
2-
import {useCallback, useMemo, useRef} from 'react';
32
import type {OnyxEntry} from 'react-native-onyx';
43
import {getNewerActions, getOlderActions} from '@userActions/Report';
54
import CONST from '@src/CONST';
@@ -11,9 +10,6 @@ type UseLoadReportActionsArguments = {
1110
/** The id of the current report */
1211
reportID: string;
1312

14-
/** The id of the reportAction (if specific action was linked to */
15-
reportActionID?: string;
16-
1713
/** The list of reportActions linked to the current report */
1814
reportActions: ReportAction[];
1915

@@ -34,121 +30,87 @@ type UseLoadReportActionsArguments = {
3430
* Provides reusable logic to get the functions for loading older/newer reportActions.
3531
* Used in the report displaying components
3632
*/
37-
function useLoadReportActions({reportID, reportActionID, reportActions, allReportActionIDs, transactionThreadReport, hasOlderActions, hasNewerActions}: UseLoadReportActionsArguments) {
38-
const didLoadOlderChats = useRef(false);
39-
const didLoadNewerChats = useRef(false);
40-
33+
function useLoadReportActions({reportID, reportActions, allReportActionIDs, transactionThreadReport, hasOlderActions, hasNewerActions}: UseLoadReportActionsArguments) {
4134
const {isOffline} = useNetwork();
4235
const isFocused = useIsFocused();
36+
const newestReportAction = reportActions?.at(0);
37+
const oldestReportAction = reportActions?.at(-1);
38+
39+
const isTransactionThreadReport = !isEmptyObject(transactionThreadReport);
40+
41+
let currentReportNewestAction = null;
42+
let currentReportOldestAction = null;
43+
let transactionThreadNewestAction = null;
44+
let transactionThreadOldestAction = null;
45+
46+
const allReportActionIDsSet = new Set(allReportActionIDs);
47+
48+
for (const action of reportActions) {
49+
// Determine which report this action belongs to
50+
const isCurrentReport = allReportActionIDsSet.has(action.reportActionID);
51+
const targetReportID = isCurrentReport ? reportID : transactionThreadReport?.reportID;
4352

44-
const newestReportAction = useMemo(() => reportActions?.at(0), [reportActions]);
45-
const oldestReportAction = useMemo(() => reportActions?.at(-1), [reportActions]);
46-
47-
// Track oldest/newest actions per report in a single pass
48-
const {currentReportOldest, currentReportNewest, transactionThreadOldest, transactionThreadNewest} = useMemo(() => {
49-
let currentReportNewestAction = null;
50-
let currentReportOldestAction = null;
51-
let transactionThreadNewestAction = null;
52-
let transactionThreadOldestAction = null;
53-
54-
const allReportActionIDsSet = new Set(allReportActionIDs);
55-
56-
for (const action of reportActions) {
57-
// Determine which report this action belongs to
58-
const isCurrentReport = allReportActionIDsSet.has(action.reportActionID);
59-
const targetReportID = isCurrentReport ? reportID : transactionThreadReport?.reportID;
60-
61-
// Track newest/oldest per report
62-
if (targetReportID === reportID) {
63-
// Newest = first matching action we encounter
64-
if (!currentReportNewestAction) {
65-
currentReportNewestAction = action;
66-
}
67-
// Oldest = last matching action we encounter
68-
currentReportOldestAction = action;
69-
} else if (!isEmptyObject(transactionThreadReport) && transactionThreadReport?.reportID === targetReportID) {
70-
// Same logic for transaction thread
71-
if (!transactionThreadNewestAction) {
72-
transactionThreadNewestAction = action;
73-
}
74-
transactionThreadOldestAction = action;
53+
// Track newest/oldest per report
54+
if (targetReportID === reportID) {
55+
// Newest = first matching action we encounter
56+
if (!currentReportNewestAction) {
57+
currentReportNewestAction = action;
7558
}
59+
// Oldest = last matching action we encounter
60+
currentReportOldestAction = action;
61+
} else if (isTransactionThreadReport && transactionThreadReport?.reportID === targetReportID) {
62+
// Same logic for transaction thread
63+
if (!transactionThreadNewestAction) {
64+
transactionThreadNewestAction = action;
65+
}
66+
transactionThreadOldestAction = action;
7667
}
77-
78-
return {
79-
currentReportOldest: currentReportOldestAction,
80-
currentReportNewest: currentReportNewestAction,
81-
transactionThreadOldest: transactionThreadOldestAction,
82-
transactionThreadNewest: transactionThreadNewestAction,
83-
};
84-
}, [reportActions, allReportActionIDs, reportID, transactionThreadReport]);
68+
}
8569

8670
/**
8771
* Retrieves the next set of reportActions for the chat once we are nearing the end of what we are currently
8872
* displaying.
8973
*/
90-
const loadOlderChats = useCallback(
91-
(force = false) => {
92-
// Only fetch more if we are neither already fetching (so that we don't initiate duplicate requests) nor offline.
93-
if (!force && isOffline) {
94-
return;
95-
}
96-
97-
// Don't load more reportActions if we're already at the beginning of the chat history
98-
if (!oldestReportAction || !hasOlderActions) {
99-
return;
100-
}
74+
const loadOlderChats = (force = false) => {
75+
// Only fetch more if we are neither already fetching (so that we don't initiate duplicate requests) nor offline.
76+
if (!force && isOffline) {
77+
return;
78+
}
10179

102-
didLoadOlderChats.current = true;
80+
// Don't load more reportActions if we're already at the beginning of the chat history
81+
if (!oldestReportAction || !hasOlderActions) {
82+
return;
83+
}
10384

104-
if (!isEmptyObject(transactionThreadReport)) {
105-
getOlderActions(reportID, currentReportOldest?.reportActionID);
106-
getOlderActions(transactionThreadReport.reportID, transactionThreadOldest?.reportActionID);
107-
} else {
108-
getOlderActions(reportID, currentReportOldest?.reportActionID);
109-
}
110-
},
111-
[isOffline, oldestReportAction, hasOlderActions, transactionThreadReport, reportID, currentReportOldest?.reportActionID, transactionThreadOldest?.reportActionID],
112-
);
113-
114-
const loadNewerChats = useCallback(
115-
(force = false) => {
116-
if (
117-
!force &&
118-
(!reportActionID ||
119-
!isFocused ||
120-
!newestReportAction ||
121-
!hasNewerActions ||
122-
isOffline ||
123-
// If there was an error only try again once on initial mount. We should also still load
124-
// more in case we have cached messages.
125-
didLoadNewerChats.current ||
126-
newestReportAction.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE)
127-
) {
128-
return;
129-
}
85+
if (isTransactionThreadReport) {
86+
getOlderActions(reportID, currentReportOldestAction?.reportActionID);
87+
getOlderActions(transactionThreadReport?.reportID, transactionThreadOldestAction?.reportActionID);
88+
} else {
89+
getOlderActions(reportID, currentReportOldestAction?.reportActionID);
90+
}
91+
};
13092

131-
didLoadNewerChats.current = true;
93+
const loadNewerChats = (force = false) => {
94+
if (
95+
!force &&
96+
(!isFocused ||
97+
!newestReportAction ||
98+
!hasNewerActions ||
99+
isOffline ||
100+
// If there was an error only try again once on initial mount. We should also still load
101+
// more in case we have cached messages.
102+
newestReportAction.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE)
103+
) {
104+
return;
105+
}
132106

133-
if (!isEmptyObject(transactionThreadReport)) {
134-
getNewerActions(reportID, currentReportNewest?.reportActionID);
135-
getNewerActions(transactionThreadReport.reportID, transactionThreadNewest?.reportActionID);
136-
} else if (newestReportAction) {
137-
getNewerActions(reportID, newestReportAction.reportActionID);
138-
}
139-
},
140-
[
141-
reportActionID,
142-
isFocused,
143-
newestReportAction,
144-
hasNewerActions,
145-
isOffline,
146-
transactionThreadReport,
147-
reportID,
148-
currentReportNewest?.reportActionID,
149-
transactionThreadNewest?.reportActionID,
150-
],
151-
);
107+
if (isTransactionThreadReport) {
108+
getNewerActions(reportID, currentReportNewestAction?.reportActionID);
109+
getNewerActions(transactionThreadReport.reportID, transactionThreadNewestAction?.reportActionID);
110+
} else if (newestReportAction) {
111+
getNewerActions(reportID, newestReportAction.reportActionID);
112+
}
113+
};
152114

153115
return {
154116
loadOlderChats,
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import {useEffect, useRef} from 'react';
2+
import type React from 'react';
3+
import {AUTOSCROLL_TO_TOP_THRESHOLD} from '@components/FlatList/hooks/useFlatListScrollKey';
4+
import usePrevious from './usePrevious';
5+
6+
type UseScrollToEndOnPaginationMergeParams = {
7+
/** The ref to the scroll offset. */
8+
scrollOffsetRef: React.RefObject<number>;
9+
/** The ID of the last visible report action. */
10+
lastActionID?: string;
11+
/** The length of the visible report actions. */
12+
visibleActionsLength: number;
13+
/** The length of the report actions. */
14+
reportActionsLength?: number;
15+
/** Whether the newest report action is the last visible report action. */
16+
hasNewestReportAction: boolean;
17+
/** The function to set the floating message counter visible. */
18+
setIsFloatingMessageCounterVisible: (isVisible: boolean) => void;
19+
/** The function to scroll to the end. */
20+
scrollToEnd: () => void;
21+
/**
22+
* Inbox uses `previousLength !== currentLength` to detect pagination merges.
23+
* Money request uses `previousLength > reportActionsLength`.
24+
*/
25+
sizeChangeType?: 'changed' | 'grewFromReportActions';
26+
/**
27+
* Included only to re-run the effect when routing/initial scroll target changes.
28+
* The value does not need to be used inside the effect.
29+
*/
30+
resetKey?: unknown;
31+
};
32+
33+
function useScrollToEndOnNewMessageReceived({
34+
scrollOffsetRef,
35+
lastActionID,
36+
visibleActionsLength,
37+
reportActionsLength,
38+
hasNewestReportAction,
39+
setIsFloatingMessageCounterVisible,
40+
scrollToEnd,
41+
sizeChangeType = 'changed',
42+
resetKey,
43+
}: UseScrollToEndOnPaginationMergeParams) {
44+
const previousLastIndex = useRef(lastActionID);
45+
const reportActionSize = useRef(visibleActionsLength);
46+
const prevHasNewestReportAction = usePrevious(hasNewestReportAction);
47+
48+
useEffect(() => {
49+
const didListSizeChange = sizeChangeType === 'grewFromReportActions' ? reportActionSize.current > (reportActionsLength ?? 0) : reportActionSize.current !== visibleActionsLength;
50+
51+
if (
52+
scrollOffsetRef.current < AUTOSCROLL_TO_TOP_THRESHOLD &&
53+
previousLastIndex.current !== lastActionID &&
54+
didListSizeChange &&
55+
hasNewestReportAction &&
56+
// We don't want to trigger a scroll to the end if the
57+
// user was not already close to the end of the chat.
58+
// When we link to a specific report action and there are
59+
// no report actions in Onyx, the report action pages might
60+
// merge, which would cause a scroll to the end otherwise.
61+
prevHasNewestReportAction
62+
) {
63+
setIsFloatingMessageCounterVisible(false);
64+
scrollToEnd();
65+
}
66+
67+
previousLastIndex.current = lastActionID;
68+
reportActionSize.current = visibleActionsLength;
69+
}, [
70+
scrollOffsetRef,
71+
lastActionID,
72+
reportActionsLength,
73+
visibleActionsLength,
74+
hasNewestReportAction,
75+
prevHasNewestReportAction,
76+
sizeChangeType,
77+
resetKey,
78+
setIsFloatingMessageCounterVisible,
79+
scrollToEnd,
80+
]);
81+
}
82+
83+
export default useScrollToEndOnNewMessageReceived;

src/libs/API/parameters/OpenReportParams.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ type OpenReportParams = {
2626
*/
2727
moneyRequestPreviewReportActionID?: string;
2828
includePartiallySetupBankAccounts?: boolean;
29+
useLastUnreadReportAction?: boolean;
2930
};
3031

3132
export default OpenReportParams;

src/libs/actions/Report/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1420,6 +1420,7 @@ function openReport(params: OpenReportActionParams) {
14201420
parentReportActionID,
14211421
transactionID: transaction?.transactionID,
14221422
includePartiallySetupBankAccounts: true,
1423+
useLastUnreadReportAction: true,
14231424
};
14241425

14251426
if (optimisticSelfDMReport) {

0 commit comments

Comments
 (0)