From 58c34fa3f81a966ff61f1981c28bd6f65b7c37a4 Mon Sep 17 00:00:00 2001 From: rory Date: Tue, 9 Jul 2024 13:06:53 -0700 Subject: [PATCH 1/2] DRY up React logic to get sorted report actions --- src/hooks/usePaginatedReportActions.ts | 39 ++++++++++++++++++ src/pages/home/ReportScreen.tsx | 57 ++++++-------------------- 2 files changed, 52 insertions(+), 44 deletions(-) create mode 100644 src/hooks/usePaginatedReportActions.ts diff --git a/src/hooks/usePaginatedReportActions.ts b/src/hooks/usePaginatedReportActions.ts new file mode 100644 index 0000000000000..b806c0dea95a2 --- /dev/null +++ b/src/hooks/usePaginatedReportActions.ts @@ -0,0 +1,39 @@ +import {useMemo} from 'react'; +import {useOnyx} from 'react-native-onyx'; +import PaginationUtils from '@libs/PaginationUtils'; +import * as ReportActionsUtils from '@libs/ReportActionsUtils'; +import ONYXKEYS from '@src/ONYXKEYS'; + +/** + * Get the longest continuous chunk of reportActions including the linked reportAction. If not linking to a specific action, returns the continuous chunk of newest reportActions. + */ +function usePaginatedReportActions(reportID?: string, reportActionID?: string) { + // Use `||` instead of `??` to handle empty string. + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + const reportIDWithDefault = reportID || '-1'; + + const [sortedAllReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportIDWithDefault}`, { + canEvict: false, + selector: (allReportActions) => ReportActionsUtils.getSortedReportActionsForDisplay(allReportActions, true), + }); + const [reportActionPages] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_PAGES}${reportIDWithDefault}`); + + const reportActions = useMemo(() => { + if (!sortedAllReportActions?.length) { + return []; + } + return PaginationUtils.getContinuousChain(sortedAllReportActions, reportActionPages ?? [], (reportAction) => reportAction.reportActionID, reportActionID); + }, [reportActionID, reportActionPages, sortedAllReportActions]); + + const linkedAction = useMemo( + () => sortedAllReportActions?.find((reportAction) => String(reportAction.reportActionID) === String(reportActionID)), + [reportActionID, sortedAllReportActions], + ); + + return { + reportActions, + linkedAction, + }; +} + +export default usePaginatedReportActions; diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 414e324484f93..54e2bb4a0e7e7 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -26,17 +26,16 @@ import useDeepCompareRef from '@hooks/useDeepCompareRef'; import useIsReportOpenInRHP from '@hooks/useIsReportOpenInRHP'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; +import usePaginatedReportActions from '@hooks/usePaginatedReportActions'; import usePermissions from '@hooks/usePermissions'; import usePrevious from '@hooks/usePrevious'; import useThemeStyles from '@hooks/useThemeStyles'; import useViewportOffsetTop from '@hooks/useViewportOffsetTop'; import useWindowDimensions from '@hooks/useWindowDimensions'; -import {getCurrentUserAccountID} from '@libs/actions/Report'; import Timing from '@libs/actions/Timing'; import Log from '@libs/Log'; import Navigation from '@libs/Navigation/Navigation'; import clearReportNotifications from '@libs/Notification/clearReportNotifications'; -import PaginationUtils from '@libs/PaginationUtils'; import Performance from '@libs/Performance'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; @@ -70,12 +69,6 @@ type ReportScreenOnyxProps = { /** The policies which the user has access to */ policies: OnyxCollection; - /** An array containing all report actions related to this report, sorted based on a date criterion */ - sortedAllReportActions: OnyxTypes.ReportAction[]; - - /** Pagination data for sortedAllReportActions */ - reportActionPages: OnyxEntry; - /** Additional report details */ reportNameValuePairs: OnyxEntry; @@ -124,8 +117,6 @@ function ReportScreen({ betas = [], route, reportNameValuePairs, - sortedAllReportActions, - reportActionPages, reportMetadata = { isLoadingInitialReportActions: true, isLoadingOlderReportActions: false, @@ -291,12 +282,9 @@ function ReportScreen({ const prevReport = usePrevious(report); const prevUserLeavingStatus = usePrevious(userLeavingStatus); const [isLinkingToMessage, setIsLinkingToMessage] = useState(!!reportActionIDFromRoute); - const reportActions = useMemo(() => { - if (!sortedAllReportActions.length) { - return []; - } - return PaginationUtils.getContinuousChain(sortedAllReportActions, reportActionPages ?? [], (reportAction) => reportAction.reportActionID, reportActionIDFromRoute); - }, [reportActionIDFromRoute, sortedAllReportActions, reportActionPages]); + + const [currentUserAccountID = -1] = useOnyx(ONYXKEYS.SESSION, {selector: (value) => value?.accountID}); + const {reportActions, linkedAction} = usePaginatedReportActions(report.reportID, reportActionIDFromRoute); // Define here because reportActions are recalculated before mount, allowing data to display faster than useEffect can trigger. // If we have cached reportActions, they will be shown immediately. @@ -321,10 +309,6 @@ function ReportScreen({ const screenWrapperStyle: ViewStyle[] = [styles.appContent, styles.flex1, {marginTop: viewportOffsetTop}]; const isEmptyChat = useMemo(() => ReportUtils.isEmptyReport(report), [report]); const isOptimisticDelete = report.statusNum === CONST.REPORT.STATUS_NUM.CLOSED; - const isLinkedMessageAvailable = useMemo( - (): boolean => sortedAllReportActions.findIndex((obj) => String(obj.reportActionID) === String(reportActionIDFromRoute)) > -1, - [sortedAllReportActions, reportActionIDFromRoute], - ); // If there's a non-404 error for the report we should show it instead of blocking the screen const hasHelpfulErrors = Object.keys(report?.errorFields ?? {}).some((key) => key !== 'notFound'); @@ -422,7 +406,7 @@ function ReportScreen({ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const isLoading = isLoadingApp || !reportIDFromRoute || (!isSidebarLoaded && !isReportOpenInRHP) || PersonalDetailsUtils.isPersonalDetailsEmpty(); const shouldShowSkeleton = - !isLinkedMessageAvailable && + !linkedAction && (isLinkingToMessage || !isCurrentReportLoadedFromOnyx || (reportActions.length === 0 && !!reportMetadata?.isLoadingInitialReportActions) || @@ -692,28 +676,22 @@ function ReportScreen({ fetchReport(); }, [fetchReport]); - const {isLinkedReportActionDeleted, isInaccessibleWhisper} = useMemo(() => { - const currentUserAccountID = getCurrentUserAccountID(); - if (!reportActionIDFromRoute || !sortedAllReportActions) { - return {isLinkedReportActionDeleted: false, isInaccessibleWhisper: false}; - } - const action = sortedAllReportActions.find((item) => item.reportActionID === reportActionIDFromRoute); - return { - isLinkedReportActionDeleted: action && !ReportActionsUtils.shouldReportActionBeVisible(action, action.reportActionID), - isInaccessibleWhisper: action && ReportActionsUtils.isWhisperAction(action) && !(action?.whisperedToAccountIDs ?? []).includes(currentUserAccountID), - }; - }, [reportActionIDFromRoute, sortedAllReportActions]); + const isLinkedActionDeleted = useMemo(() => !!linkedAction && !ReportActionsUtils.shouldReportActionBeVisible(linkedAction, linkedAction.reportActionID), [linkedAction]); + const isLinkedActionInaccessibleWhisper = useMemo( + () => !!linkedAction && ReportActionsUtils.isWhisperAction(linkedAction) && !(linkedAction?.whisperedToAccountIDs ?? []).includes(currentUserAccountID), + [currentUserAccountID, linkedAction], + ); // If user redirects to an inaccessible whisper via a deeplink, on a report they have access to, // then we set reportActionID as empty string, so we display them the report and not the "Not found page". useEffect(() => { - if (!isInaccessibleWhisper) { + if (!isLinkedActionInaccessibleWhisper) { return; } Navigation.isNavigationReady().then(() => { Navigation.setParams({reportActionID: ''}); }); - }, [isInaccessibleWhisper]); + }, [isLinkedActionInaccessibleWhisper]); useEffect(() => { if (!!report.lastReadTime || !ReportUtils.isTaskReport(report)) { @@ -723,7 +701,7 @@ function ReportScreen({ Report.readNewestAction(report.reportID); }, [report]); - if ((!isInaccessibleWhisper && isLinkedReportActionDeleted) ?? (!shouldShowSkeleton && reportActionIDFromRoute && reportActions?.length === 0 && !isLinkingToMessage)) { + if ((!isLinkedActionInaccessibleWhisper && isLinkedActionDeleted) ?? (!shouldShowSkeleton && reportActionIDFromRoute && reportActions?.length === 0 && !isLinkingToMessage)) { return ( `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${getReportID(route)}`, - canEvict: false, - selector: (allReportActions: OnyxEntry) => ReportActionsUtils.getSortedReportActionsForDisplay(allReportActions, true), - }, - reportActionPages: { - key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS_PAGES}${getReportID(route)}`, - }, reportNameValuePairs: { key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${getReportID(route)}`, allowStaleData: true, @@ -874,7 +844,6 @@ export default withCurrentReportID( ReportScreen, (prevProps, nextProps) => prevProps.isSidebarLoaded === nextProps.isSidebarLoaded && - lodashIsEqual(prevProps.sortedAllReportActions, nextProps.sortedAllReportActions) && lodashIsEqual(prevProps.reportMetadata, nextProps.reportMetadata) && lodashIsEqual(prevProps.betas, nextProps.betas) && lodashIsEqual(prevProps.policies, nextProps.policies) && From 99afbbe6ee3785133d737afa5dc30ff8e7d8c1c3 Mon Sep 17 00:00:00 2001 From: rory Date: Tue, 9 Jul 2024 13:08:42 -0700 Subject: [PATCH 2/2] Use usePaginatedReportActions in ReportDetailsPage --- src/pages/ReportDetailsPage.tsx | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index c9440ee548af8..6f3ca95102cd9 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -24,11 +24,11 @@ import ScrollView from '@components/ScrollView'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; +import usePaginatedReportActions from '@hooks/usePaginatedReportActions'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import type {ReportDetailsNavigatorParamList} from '@libs/Navigation/types'; import * as OptionsListUtils from '@libs/OptionsListUtils'; -import PaginationUtils from '@libs/PaginationUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; @@ -86,18 +86,7 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD // The app would crash due to subscribing to the entire report collection if parentReportID is an empty string. So we should have a fallback ID here. // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID || '-1'}`); - const [sortedAllReportActions = []] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID || '-1'}`, { - canEvict: false, - selector: (allReportActions: OnyxEntry) => ReportActionsUtils.getSortedReportActionsForDisplay(allReportActions, true), - }); - const [reportActionPages = []] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_PAGES}${report.reportID || '-1'}`); - - const reportActions = useMemo(() => { - if (!sortedAllReportActions.length) { - return []; - } - return PaginationUtils.getContinuousChain(sortedAllReportActions, reportActionPages, (reportAction) => reportAction.reportActionID); - }, [sortedAllReportActions, reportActionPages]); + const {reportActions} = usePaginatedReportActions(report.reportID || '-1'); const transactionThreadReportID = useMemo( () => ReportActionsUtils.getOneTransactionThreadReportID(report.reportID, reportActions ?? [], isOffline),