Skip to content

Commit 122081b

Browse files
authored
Merge pull request Expensify#89598 from callstack-internal/refactor/extract-report-actions-pagination-hooks
decompose ReportActionsList: 2
2 parents c7b5c45 + 6fd8bc2 commit 122081b

6 files changed

Lines changed: 208 additions & 111 deletions

File tree

src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ function MoneyRequestReportActionsList({onLayout}: MoneyRequestReportListProps)
217217
reportID,
218218
reportActions,
219219
allReportActionIDs: reportActionIDs,
220-
transactionThreadReport,
220+
transactionThreadReportID,
221221
hasOlderActions,
222222
hasNewerActions,
223223
newestFetchedReportActionID: reportPaginationState?.newestFetchedReportActionID,

src/hooks/useLoadReportActions.ts

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import {useIsFocused} from '@react-navigation/native';
2-
import type {OnyxEntry} from 'react-native-onyx';
32
import {getNewerActions, getOlderActions} from '@userActions/Report';
43
import CONST from '@src/CONST';
5-
import type {Report, ReportAction} from '@src/types/onyx';
6-
import {isEmptyObject} from '@src/types/utils/EmptyObject';
4+
import type {ReportAction} from '@src/types/onyx';
75
import useNetwork from './useNetwork';
86

97
type UseLoadReportActionsArguments = {
@@ -16,8 +14,8 @@ type UseLoadReportActionsArguments = {
1614
/** The IDs of all reportActions linked to the current report (may contain some extra actions) */
1715
allReportActionIDs: string[];
1816

19-
/** The transaction thread report associated with the current transaction, if any */
20-
transactionThreadReport: OnyxEntry<Report>;
17+
/** The transaction thread report ID associated with the current transaction, if any */
18+
transactionThreadReportID: string | undefined;
2119

2220
/** If the report has newer actions to load */
2321
hasNewerActions: boolean;
@@ -37,7 +35,7 @@ function useLoadReportActions({
3735
reportID,
3836
reportActions,
3937
allReportActionIDs,
40-
transactionThreadReport,
38+
transactionThreadReportID,
4139
hasOlderActions,
4240
hasNewerActions,
4341
newestFetchedReportActionID,
@@ -47,7 +45,7 @@ function useLoadReportActions({
4745
const newestReportAction = reportActions?.at(0);
4846
const oldestReportAction = reportActions?.at(-1);
4947

50-
const isTransactionThreadReport = !isEmptyObject(transactionThreadReport);
48+
const isTransactionThreadReport = !!transactionThreadReportID && transactionThreadReportID !== CONST.FAKE_REPORT_ID;
5149

5250
let currentReportNewestAction = null;
5351
let currentReportOldestAction = null;
@@ -59,7 +57,7 @@ function useLoadReportActions({
5957
for (const action of reportActions) {
6058
// Determine which report this action belongs to
6159
const isCurrentReport = allReportActionIDsSet.has(action.reportActionID);
62-
const targetReportID = isCurrentReport ? reportID : transactionThreadReport?.reportID;
60+
const targetReportID = isCurrentReport ? reportID : transactionThreadReportID;
6361

6462
// Track newest/oldest per report
6563
if (targetReportID === reportID) {
@@ -69,7 +67,7 @@ function useLoadReportActions({
6967
}
7068
// Oldest = last matching action we encounter
7169
currentReportOldestAction = action;
72-
} else if (isTransactionThreadReport && transactionThreadReport?.reportID === targetReportID) {
70+
} else if (isTransactionThreadReport && transactionThreadReportID === targetReportID) {
7371
// Same logic for transaction thread
7472
if (!transactionThreadNewestAction) {
7573
transactionThreadNewestAction = action;
@@ -95,7 +93,7 @@ function useLoadReportActions({
9593

9694
if (isTransactionThreadReport) {
9795
getOlderActions(reportID, currentReportOldestAction?.reportActionID);
98-
getOlderActions(transactionThreadReport?.reportID, transactionThreadOldestAction?.reportActionID);
96+
getOlderActions(transactionThreadReportID, transactionThreadOldestAction?.reportActionID);
9997
} else {
10098
getOlderActions(reportID, currentReportOldestAction?.reportActionID);
10199
}
@@ -124,7 +122,7 @@ function useLoadReportActions({
124122

125123
if (isTransactionThreadReport) {
126124
getNewerActions(reportID, currentReportNewestAction?.reportActionID);
127-
getNewerActions(transactionThreadReport.reportID, transactionThreadNewestAction?.reportActionID);
125+
getNewerActions(transactionThreadReportID, transactionThreadNewestAction?.reportActionID);
128126
} else if (newestReportAction) {
129127
getNewerActions(reportID, newestReportAction.reportActionID);
130128
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import {useMemo, useState} from 'react';
2+
import type {OnyxEntry} from 'react-native-onyx';
3+
import {getReportPreviewAction} from '@libs/actions/IOU/MoneyRequestBuilder';
4+
import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
5+
import {getCombinedReportActions, getFilteredReportActionsForReportView, isCreatedAction} from '@libs/ReportActionsUtils';
6+
import {isConciergeChatReport, isInvoiceReport, isMoneyRequestReport, isReportTransactionThread as isReportTransactionThreadUtil} from '@libs/ReportUtils';
7+
import getReportActionsToDisplay from '@pages/inbox/report/getReportActionsToDisplay';
8+
import ONYXKEYS from '@src/ONYXKEYS';
9+
import type {Pages, Report, ReportAction} from '@src/types/onyx';
10+
import useIsInSidePanel from './useIsInSidePanel';
11+
import useNetwork from './useNetwork';
12+
import useOnyx from './useOnyx';
13+
import usePaginatedReportActions from './usePaginatedReportActions';
14+
import useTransactionThread from './useTransactionThread';
15+
16+
type UseReportActionsPaginationResult = {
17+
reportActions: ReportAction[];
18+
allReportActions: ReportAction[];
19+
allReportActionIDs: string[];
20+
hasOlderActions: boolean;
21+
hasNewerActions: boolean;
22+
sortedAllReportActions: ReportAction[] | undefined;
23+
oldestUnreadReportAction: ReportAction | undefined;
24+
reportActionPages: OnyxEntry<Pages>;
25+
transactionThreadReportID: string | undefined;
26+
transactionThreadReport: OnyxEntry<Report>;
27+
parentReportActionForTransactionThread: ReportAction | undefined;
28+
shouldAddCreatedAction: boolean;
29+
treatAsNoPaginationAnchor: boolean;
30+
setTreatAsNoPaginationAnchor: (value: boolean) => void;
31+
};
32+
33+
function useReportActionsPagination(reportID: string | undefined, reportActionIDFromRoute: string | undefined): UseReportActionsPaginationResult {
34+
const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`);
35+
const {isOffline} = useNetwork();
36+
37+
const [treatAsNoPaginationAnchor, setTreatAsNoPaginationAnchor] = useState(false);
38+
const nonEmptyReportIDForPages = getNonEmptyStringOnyxID(reportID);
39+
const [reportActionPages] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_PAGES}${nonEmptyReportIDForPages}`);
40+
41+
const {
42+
reportActions: unfilteredReportActions,
43+
hasOlderActions,
44+
hasNewerActions,
45+
sortedAllReportActions,
46+
oldestUnreadReportAction,
47+
} = usePaginatedReportActions(reportID, reportActionIDFromRoute, {
48+
shouldLinkToOldestUnreadReportAction: true,
49+
treatAsNoPaginationAnchor,
50+
});
51+
const allReportActions = useMemo(() => getFilteredReportActionsForReportView(unfilteredReportActions), [unfilteredReportActions]);
52+
53+
const thread = useTransactionThread({reportID, report, allReportActions, isOffline});
54+
55+
const isInSidePanel = useIsInSidePanel();
56+
const [conciergeReportID] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID);
57+
const isConciergeSidePanel = isInSidePanel && isConciergeChatReport(report, conciergeReportID);
58+
59+
const [reportLoadingState] = useOnyx(`${ONYXKEYS.COLLECTION.RAM_ONLY_REPORT_LOADING_STATE}${reportID}`);
60+
const isLoadingInitialReportActions = reportLoadingState?.isLoadingInitialReportActions;
61+
62+
const isReportTransactionThread = isReportTransactionThreadUtil(report);
63+
const isInitiallyLoadingTransactionThread = isReportTransactionThread && (!!isLoadingInitialReportActions || (allReportActions ?? [])?.length <= 1);
64+
65+
const lastAction = allReportActions?.at(-1);
66+
const shouldAddCreatedAction = !isCreatedAction(lastAction) && (isMoneyRequestReport(report) || isInvoiceReport(report) || isInitiallyLoadingTransactionThread || isConciergeSidePanel);
67+
68+
const reportPreviewAction = useMemo(() => getReportPreviewAction(report?.chatReportID, report?.reportID), [report?.chatReportID, report?.reportID]);
69+
70+
// When we are offline before opening an IOU/Expense report,
71+
// the total of the report and sometimes the expense aren't displayed because these actions aren't returned until `OpenReport` API is complete.
72+
// We generate a fake created action here if it doesn't exist to display the total whenever possible because the total just depends on report data
73+
// and we also generate an expense action if the number of expenses in allReportActions is less than the total number of expenses
74+
// to display at least one expense action to match the total data.
75+
const reportActionsToDisplay = useMemo(
76+
() => getReportActionsToDisplay(allReportActions, lastAction, report, reportPreviewAction, thread.transactionThreadReport, shouldAddCreatedAction),
77+
[allReportActions, lastAction, report, reportPreviewAction, shouldAddCreatedAction, thread.transactionThreadReport],
78+
);
79+
80+
const reportActions = useMemo(
81+
() => (reportActionsToDisplay ? getCombinedReportActions(reportActionsToDisplay, thread.transactionThreadReportID ?? null, thread.transactionThreadReportActions ?? []) : []),
82+
[reportActionsToDisplay, thread.transactionThreadReportActions, thread.transactionThreadReportID],
83+
);
84+
85+
const allReportActionIDs = useMemo(() => allReportActions.map((action) => action.reportActionID), [allReportActions]);
86+
87+
return {
88+
reportActions,
89+
allReportActions,
90+
allReportActionIDs,
91+
hasOlderActions,
92+
hasNewerActions,
93+
sortedAllReportActions,
94+
oldestUnreadReportAction,
95+
reportActionPages,
96+
transactionThreadReportID: thread.transactionThreadReportID,
97+
transactionThreadReport: thread.transactionThreadReport,
98+
parentReportActionForTransactionThread: thread.parentReportActionForTransactionThread,
99+
shouldAddCreatedAction,
100+
treatAsNoPaginationAnchor,
101+
setTreatAsNoPaginationAnchor,
102+
};
103+
}
104+
105+
export default useReportActionsPagination;

src/hooks/useTransactionThread.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import type {OnyxEntry} from 'react-native-onyx';
2+
import {getAllNonDeletedTransactions} from '@libs/MoneyRequestReportUtils';
3+
import {getOneTransactionThreadReportID, getSortedReportActionsForDisplay} from '@libs/ReportActionsUtils';
4+
import {canUserPerformWriteAction} from '@libs/ReportUtils';
5+
import CONST from '@src/CONST';
6+
import ONYXKEYS from '@src/ONYXKEYS';
7+
import type {Report, ReportAction, ReportActions} from '@src/types/onyx';
8+
import {isEmptyObject} from '@src/types/utils/EmptyObject';
9+
import useOnyx from './useOnyx';
10+
import useReportIsArchived from './useReportIsArchived';
11+
import useReportTransactionsCollection from './useReportTransactionsCollection';
12+
13+
type UseTransactionThreadParams = {
14+
reportID: string | undefined;
15+
report: OnyxEntry<Report>;
16+
allReportActions: ReportAction[];
17+
isOffline: boolean;
18+
};
19+
20+
type UseTransactionThreadResult = {
21+
transactionThreadReportID: string | undefined;
22+
transactionThreadReportActions: ReportAction[] | undefined;
23+
transactionThreadReport: OnyxEntry<Report>;
24+
parentReportActionForTransactionThread: ReportAction | undefined;
25+
};
26+
27+
function selectTransactionThreadReportActions(
28+
canPerformWriteAction: boolean,
29+
transactionThreadReportID: string | undefined,
30+
reportActions: OnyxEntry<ReportActions> | undefined,
31+
): ReportAction[] {
32+
return getSortedReportActionsForDisplay(reportActions, canPerformWriteAction, true, undefined, transactionThreadReportID ?? undefined);
33+
}
34+
35+
function useTransactionThread({reportID, report, allReportActions, isOffline}: UseTransactionThreadParams): UseTransactionThreadResult {
36+
const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report?.chatReportID}`);
37+
38+
const allReportTransactions = useReportTransactionsCollection(reportID);
39+
40+
const reportTransactionsForThreadID = getAllNonDeletedTransactions(allReportTransactions, allReportActions ?? [], isOffline, true);
41+
42+
const visibleTransactionsForThreadID = reportTransactionsForThreadID?.filter((transaction) => isOffline || transaction.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE);
43+
44+
const reportTransactionIDsForThread = visibleTransactionsForThreadID?.map((t) => t.transactionID);
45+
46+
const transactionThreadReportID = getOneTransactionThreadReportID(report, chatReport, allReportActions ?? [], isOffline, reportTransactionIDsForThread);
47+
48+
const isReportArchived = useReportIsArchived(reportID);
49+
const canPerformWriteAction = canUserPerformWriteAction(report, isReportArchived);
50+
51+
const [transactionThreadReportActions] = useOnyx(
52+
`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadReportID}`,
53+
{
54+
selector: (reportActions) => selectTransactionThreadReportActions(!!canPerformWriteAction, transactionThreadReportID, reportActions),
55+
},
56+
[canPerformWriteAction, transactionThreadReportID],
57+
);
58+
59+
const [transactionThreadReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`);
60+
61+
const parentReportActionForTransactionThread = isEmptyObject(transactionThreadReportActions)
62+
? undefined
63+
: allReportActions?.find((action) => action.reportActionID === transactionThreadReport?.parentReportActionID);
64+
65+
return {
66+
transactionThreadReportID,
67+
transactionThreadReportActions,
68+
transactionThreadReport,
69+
parentReportActionForTransactionThread,
70+
};
71+
}
72+
73+
export default useTransactionThread;

0 commit comments

Comments
 (0)