diff --git a/src/pages/inbox/ReportNotFoundGuard.tsx b/src/pages/inbox/ReportNotFoundGuard.tsx index c5ce99b3becf6..18043205baefc 100644 --- a/src/pages/inbox/ReportNotFoundGuard.tsx +++ b/src/pages/inbox/ReportNotFoundGuard.tsx @@ -20,18 +20,25 @@ type ReportNotFoundGuardProps = { children: ReactNode; }; +/** + * Two-level gate: the outer guard subscribes to lightweight keys to determine + * whether the report clearly exists. When it does (and the report is not a + * transaction thread), children render directly without the cost of + * parentReportMetadata / useParentReportAction subscriptions. + * + * The inner gate mounts only when the "not found" path is plausible: + * the report is missing, the path is invalid, or the report is a transaction + * thread whose parent action may have been deleted. + */ // eslint-disable-next-line rulesdir/no-negated-variables function ReportNotFoundGuard({children}: ReportNotFoundGuardProps) { - const styles = useThemeStyles(); const route = useRoute(); const routeParams = route.params as {reportID?: string} | undefined; const reportIDFromRoute = getNonEmptyStringOnyxID(routeParams?.reportID); const {isOffline} = useNetwork(); - const {shouldUseNarrowLayout} = useResponsiveLayout(); const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportIDFromRoute}`); - const [parentReportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${report?.parentReportID}`); const [userLeavingStatus = false] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_USER_IS_LEAVING_ROOM}${reportIDFromRoute}`); const [isLoadingInitialReportActions = true] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${reportIDFromRoute}`, { selector: isLoadingInitialReportActionsSelector, @@ -40,9 +47,60 @@ function ReportNotFoundGuard({children}: ReportNotFoundGuardProps) { const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP); const [deleteTransactionNavigateBackUrl] = useOnyx(ONYXKEYS.NVP_DELETE_TRANSACTION_NAVIGATE_BACK_URL); + const reportID = report?.reportID; + const isOptimisticDelete = report?.statusNum === CONST.REPORT.STATUS_NUM.CLOSED; + const isInvalidReportPath = !!routeParams?.reportID && !isValidReportIDFromPath(routeParams.reportID); + const isLoading = isLoadingApp !== false || isLoadingReportData || (!isOffline && !!isLoadingInitialReportActions); + const mayBeTransactionThread = isReportTransactionThread(report); + + // Fast path: skip the expensive inner guard (parentReportMetadata, useParentReportAction) + // when we can determine shouldShowNotFoundPage is definitely false. + // + // shouldShowNotFoundPage is false when: + // - deleteTransactionNavigateBackUrl is set (always suppresses not-found), OR + // - path is valid AND report is not a transaction thread AND (still loading OR report exists) + const reportClearlyExists = !!reportID || isOptimisticDelete || userLeavingStatus; + const canSkipInnerGuard = !!deleteTransactionNavigateBackUrl || (!isInvalidReportPath && !mayBeTransactionThread && (isLoading || reportClearlyExists)); + if (canSkipInnerGuard) { + return children; + } + + return {children}; +} + +type ReportNotFoundInnerGuardProps = { + reportIDFromPath: string | undefined; + children: ReactNode; +}; + +/** + * Inner guard that mounts only when the "not found" path is plausible. + * Re-derives all state from its own hooks (Onyx returns cached values + * synchronously, so the extra subscriptions are near-zero cost). + */ +// eslint-disable-next-line rulesdir/no-negated-variables +function ReportNotFoundInnerGuard({reportIDFromPath, children}: ReportNotFoundInnerGuardProps) { + const styles = useThemeStyles(); + const {shouldUseNarrowLayout} = useResponsiveLayout(); + const {isOffline} = useNetwork(); + + const reportIDFromRoute = getNonEmptyStringOnyxID(reportIDFromPath); + + const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportIDFromRoute}`); + const [userLeavingStatus = false] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_USER_IS_LEAVING_ROOM}${reportIDFromRoute}`); + const [isLoadingInitialReportActions = true] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${reportIDFromRoute}`, { + selector: isLoadingInitialReportActionsSelector, + }); + const [isLoadingReportData = true] = useOnyx(ONYXKEYS.IS_LOADING_REPORT_DATA); + const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP); + const [deleteTransactionNavigateBackUrl] = useOnyx(ONYXKEYS.NVP_DELETE_TRANSACTION_NAVIGATE_BACK_URL); + const [parentReportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${report?.parentReportID}`); const parentReportAction = useParentReportAction(report); + const reportID = report?.reportID; const isOptimisticDelete = report?.statusNum === CONST.REPORT.STATUS_NUM.CLOSED; + const isInvalidReportPath = !!reportIDFromPath && !isValidReportIDFromPath(reportIDFromPath); + const isLoading = isLoadingApp !== false || isLoadingReportData || (!isOffline && !!isLoadingInitialReportActions); const {isParentActionMissingAfterLoad, isParentActionDeleted} = getParentReportActionDeletionStatus({ parentReportID: report?.parentReportID, @@ -53,8 +111,6 @@ function ReportNotFoundGuard({children}: ReportNotFoundGuardProps) { }); const isDeletedTransactionThread = isReportTransactionThread(report) && (isParentActionDeleted || isParentActionMissingAfterLoad); - const isInvalidReportPath = !!routeParams?.reportID && !isValidReportIDFromPath(routeParams.reportID); - const isLoading = isLoadingApp !== false || isLoadingReportData || (!isOffline && !!isLoadingInitialReportActions); const reportExists = !!reportID || (!isDeletedTransactionThread && isOptimisticDelete) || userLeavingStatus; // eslint-disable-next-line rulesdir/no-negated-variables @@ -73,7 +129,7 @@ function ReportNotFoundGuard({children}: ReportNotFoundGuardProps) { reportID, isOptimisticDelete, userLeavingStatus, - reportIDFromPath: routeParams?.reportID, + reportIDFromPath, deleteTransactionNavigateBackUrl, isDeletedTransactionThread, isParentActionDeleted, @@ -88,7 +144,7 @@ function ReportNotFoundGuard({children}: ReportNotFoundGuardProps) { reportID, isOptimisticDelete, userLeavingStatus, - routeParams?.reportID, + reportIDFromPath, deleteTransactionNavigateBackUrl, isDeletedTransactionThread, isParentActionDeleted,