-
Notifications
You must be signed in to change notification settings - Fork 3.7k
fix: prevent Concierge redirect and LHN disappearance when vacation delegate splits expense #86869
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -68,6 +68,12 @@ function ReportFetchHandler() { | |
| const hasCreatedLegacyThreadRef = useRef(false); | ||
| const didSubscribeToReportLeavingEvents = useRef(false); | ||
|
|
||
| // Track whether the current route is an own workspace chat synchronously during render. | ||
| // After a delegate split the server sends an Onyx SET that wipes the report; by the time a | ||
| // useEffect fires, report is undefined and we can no longer detect the chat type. See #84248. | ||
| const isCurrentRouteOwnWorkspaceChatRef = useRef(false); | ||
| // (Updated below after reportOnyx is read.) | ||
|
|
||
| const [reportOnyx] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportIDFromRoute}`); | ||
| const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportOnyx?.chatReportID}`); | ||
| const [reportMetadata = defaultReportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${reportIDFromRoute}`); | ||
|
|
@@ -98,6 +104,16 @@ function ReportFetchHandler() { | |
|
|
||
| const isTransactionThreadView = isReportTransactionThread(report); | ||
|
|
||
| // Update the ref synchronously each render so the re-fetch effect below can read it | ||
| // even after the Onyx wipe has cleared `report`. | ||
| if (report?.reportID && report.reportID === reportIDFromRoute) { | ||
| isCurrentRouteOwnWorkspaceChatRef.current = !!report.isOwnPolicyExpenseChat; | ||
| } else if (!report?.reportID) { | ||
| // Report wiped — intentionally keep the last known value. | ||
| } else { | ||
| isCurrentRouteOwnWorkspaceChatRef.current = false; | ||
| } | ||
|
|
||
| const indexOfLinkedMessage = reportActionIDFromRoute ? reportActions.findIndex((obj) => String(obj.reportActionID) === String(reportActionIDFromRoute)) : -1; | ||
| const doesCreatedActionExists = !!reportActions?.findLast((action) => isCreatedAction(action)); | ||
| const isLinkedMessageAvailable = indexOfLinkedMessage > -1; | ||
|
|
@@ -156,6 +172,20 @@ function ReportFetchHandler() { | |
|
|
||
| // Effect order below matches the original declaration order in ReportScreen.tsx. | ||
|
|
||
| // When a delegate splits an expense the server sends a temporary Onyx SET that wipes the | ||
| // workspace chat. The navigation guards in ReportScreen block any redirect, but the report | ||
| // stays blank until something re-fetches it. This effect detects the wipe and re-fetches. | ||
| // See issue #84248. | ||
| const prevReportID = usePrevious(report?.reportID); | ||
| useEffect(() => { | ||
| const wasJustWiped = !!prevReportID && prevReportID === reportIDFromRoute && !report?.reportID; | ||
| if (!wasJustWiped || !isCurrentRouteOwnWorkspaceChatRef.current) { | ||
| return; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❌ CONSISTENCY-5 (docs)The Add a comment above or on the same line, for example: // fetchReport is a stable useEffectEvent callback and does not need to be listed as a dependency.
// eslint-disable-next-line react-hooks/exhaustive-depsPlease rate this suggestion with 👍 or 👎 to help us improve! Reactions are used to monitor reviewer efficiency.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added justification comment above the eslint-disable line. Updated |
||
| } | ||
| fetchReport(); | ||
| // eslint-disable-next-line react-hooks/exhaustive-deps | ||
| }, [report?.reportID, prevReportID, reportIDFromRoute]); | ||
|
|
||
| useEffect(() => { | ||
| if (!transactionThreadReportID || !route?.params?.reportActionID || !isOneTransactionThread(childReport, report, linkedAction)) { | ||
| return; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
| import {PortalHost} from '@gorhom/portal'; | ||
| import {useIsFocused} from '@react-navigation/native'; | ||
| import React, {useCallback, useEffect, useMemo, useState} from 'react'; | ||
| import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; | ||
| import type {ViewStyle} from 'react-native'; | ||
| // We use Animated for all functionality related to wide RHP to make it easier | ||
| // to interact with react-navigation components (e.g., CardContainer, interpolator), which also use Animated. | ||
|
|
@@ -354,6 +354,20 @@ function ReportScreen({route, navigation}: ReportScreenProps) { | |
| }); | ||
| const isDeletedTransactionThread = isReportTransactionThread(report) && (isParentActionDeleted || isParentActionMissingAfterLoad); | ||
| const [deleteTransactionNavigateBackUrl] = useOnyx(ONYXKEYS.NVP_DELETE_TRANSACTION_NAVIGATE_BACK_URL); | ||
|
|
||
| // Track whether the current route is an own workspace chat (isOwnPolicyExpenseChat). | ||
| // Must be a ref set synchronously during render — by the time the navigation effects fire | ||
| // after a delegate split, the server SET has wiped report/prevReport in Onyx so we can't | ||
| // rely on live state or usePrevious. See issue #84248. | ||
|
||
| const isCurrentRouteOwnWorkspaceChatRef = useRef(false); | ||
| if (report?.reportID && report.reportID === reportIDFromRoute) { | ||
| isCurrentRouteOwnWorkspaceChatRef.current = !!report.isOwnPolicyExpenseChat; | ||
| } else if (!report?.reportID) { | ||
| // Report wiped by Onyx SET — intentionally keep the last known value. | ||
| } else { | ||
| isCurrentRouteOwnWorkspaceChatRef.current = false; | ||
| } | ||
|
|
||
| // eslint-disable-next-line rulesdir/no-negated-variables | ||
| const shouldShowNotFoundLinkedAction = | ||
| (!isLinkedActionInaccessibleWhisper && isLinkedActionDeleted && isNavigatingToDeletedAction) || | ||
|
|
@@ -473,7 +487,10 @@ function ReportScreen({route, navigation}: ReportScreenProps) { | |
| isEmpty(report) && | ||
| (isMoneyRequest(prevReport) || | ||
| isMoneyRequestReport(prevReport) || | ||
| isPolicyExpenseChat(prevReport) || | ||
| // Own policy expense chats (workspace chats) are excluded: a vacation delegate | ||
| // splitting an expense sends a temporary server SET that wipes the report, but | ||
| // the chat was never intentionally removed. See issue #84248. | ||
| (isPolicyExpenseChat(prevReport) && !prevReport?.isOwnPolicyExpenseChat) || | ||
| isGroupChat(prevReport) || | ||
| isAdminRoom(prevReport) || | ||
| isAnnounceRoom(prevReport)); | ||
|
|
@@ -566,6 +583,22 @@ function ReportScreen({route, navigation}: ReportScreenProps) { | |
| return; | ||
| } | ||
|
|
||
| // Do not navigate away for own workspace chats — a delegate split causes a temporary | ||
| // Onyx wipe that looks like a deletion but the chat was never actually removed. | ||
| // See issue #84248. | ||
| if (isCurrentRouteOwnWorkspaceChatRef.current) { | ||
| return; | ||
|
||
| } | ||
|
|
||
| // Clean up the navigation stack before redirecting to prevent an infinite loop where | ||
| // pressing back returns to the wiped report URL and re-triggers this effect. | ||
| Navigation.dismissModal(); | ||
| if (Navigation.getTopmostReportId() === reportIDFromRoute) { | ||
| Navigation.isNavigationReady().then(() => { | ||
| Navigation.popToSidebar(); | ||
| }); | ||
| } | ||
|
|
||
| // Try to navigate to parent report if available | ||
| if (deletedReportParentID && !isMoneyRequestReportPendingDeletion(deletedReportParentID)) { | ||
| Navigation.isNavigationReady().then(() => { | ||
|
|
@@ -578,7 +611,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { | |
| Navigation.isNavigationReady().then(() => { | ||
| navigateToConciergeChat(conciergeReportID, introSelected, currentUserAccountID, isSelfTourViewed, betas); | ||
| }); | ||
| }, [reportWasDeleted, isFocused, deletedReportParentID, conciergeReportID, introSelected, currentUserAccountID, isSelfTourViewed, betas]); | ||
| }, [reportWasDeleted, isFocused, deletedReportParentID, conciergeReportID, introSelected, currentUserAccountID, isSelfTourViewed, betas, reportIDFromRoute]); | ||
|
|
||
| const actionListValue = useActionListContextValue(); | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❌ CONSISTENCY-3 (docs)
This ref-tracking block is nearly identical to the one in
ReportScreen.tsx(lines 361-370). Both checkreport?.reportID, compare againstreportIDFromRoute, setisOwnPolicyExpenseChat, and intentionally preserve the last-known value when the report is wiped. Extract a shared hook such asuseIsOwnWorkspaceChatRef(report, reportIDFromRoute)that returns the ref, so both files can consume it without duplicating the logic.Please rate this suggestion with 👍 or 👎 to help us improve! Reactions are used to monitor reviewer efficiency.