Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions src/DeepLinkHandler.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ function DeepLinkHandler({onInitialUrl}: DeepLinkHandlerProps) {

const [allReports, allReportsMetadata] = useOnyx(ONYXKEYS.COLLECTION.REPORT);
const [isLoadingApp = true] = useOnyx(ONYXKEYS.IS_LOADING_APP);
const [, sessionMetadata] = useOnyx(ONYXKEYS.SESSION);
const [session, sessionMetadata] = useOnyx(ONYXKEYS.SESSION);
const [conciergeReportID, conciergeReportIDMetadata] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID);
const [introSelected, introSelectedMetadata] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED);
const [isSelfTourViewed, isSelfTourViewedMetadata] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {selector: hasSeenTourSelector});
Expand Down Expand Up @@ -103,7 +103,16 @@ function DeepLinkHandler({onInitialUrl}: DeepLinkHandlerProps) {
if (introSelected === undefined) {
Log.info('[Deep link] introSelected is undefined when processing initial URL', false, {url});
}
openReportFromDeepLink(url, allReports, isCurrentlyAuthenticated, conciergeReportID, introSelected, isSelfTourViewed, betas);
openReportFromDeepLink(
url,
allReports,
isCurrentlyAuthenticated,
conciergeReportID,
introSelected,
isSelfTourViewed,
betas,
session?.accountID ?? CONST.DEFAULT_NUMBER_ID,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid freezing the signed-out account ID for deep links

When a logged-out user opens a report deep link, this captures CONST.DEFAULT_NUMBER_ID before waitForUserSignIn() completes; openReportFromDeepLink() later uses that same value in the Concierge fallback when the linked report is missing or inaccessible. Since this effect only depends on sessionMetadata.status and the Link module's live session subscription was removed, the fallback can open/create Concierge for account 0 instead of the account that just signed in. Pass a live/ref-updated account ID or resolve it after sign-in rather than freezing the signed-out value here.

Useful? React with 👍 / 👎.

);
trackPendingPublicRoomFromDeepLink(url, isCurrentlyAuthenticated);
} else {
Report.doneCheckingPublicRoom();
Expand Down Expand Up @@ -131,7 +140,7 @@ function DeepLinkHandler({onInitialUrl}: DeepLinkHandlerProps) {
Log.info('[Deep link] introSelected is undefined when processing URL change', false, {url: state.url});
}
const isCurrentlyAuthenticated = hasAuthToken();
openReportFromDeepLink(state.url, allReports, isCurrentlyAuthenticated, conciergeReportID, introSelected, isSelfTourViewed, betas);
openReportFromDeepLink(state.url, allReports, isCurrentlyAuthenticated, conciergeReportID, introSelected, isSelfTourViewed, betas, session?.accountID ?? CONST.DEFAULT_NUMBER_ID);
trackPendingPublicRoomFromDeepLink(state.url, isCurrentlyAuthenticated);
});

Expand Down Expand Up @@ -184,8 +193,8 @@ function DeepLinkHandler({onInitialUrl}: DeepLinkHandlerProps) {
return;
}
hasRefetchedPublicRoom.current = true;
Report.openReport({reportID, introSelected, betas});
}, [isLoadingApp, allReports, introSelected, betas]);
Report.openReport({reportID, introSelected, betas, currentUserAccountID: session?.accountID});
}, [isLoadingApp, allReports, introSelected, betas, session?.accountID]);

return null;
}
Expand Down
6 changes: 2 additions & 4 deletions src/libs/actions/Link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,11 @@ import {canAnonymousUserAccessRoute, isAnonymousUser, signOutAndRedirectToSignIn
import {setOnboardingErrorMessage} from './Welcome';

let currentUserEmail = '';
let currentUserAccountID = -1;
// Use connectWithoutView since this is to open an external link and doesn't affect any UI
Onyx.connectWithoutView({
key: ONYXKEYS.SESSION,
callback: (value) => {
currentUserEmail = value?.email ?? '';
currentUserAccountID = value?.accountID ?? CONST.DEFAULT_NUMBER_ID;
},
});

Expand Down Expand Up @@ -459,6 +457,7 @@ function openReportFromDeepLink(
introSelected: OnyxEntry<IntroSelected>,
isSelfTourViewed: boolean | undefined,
betas: OnyxEntry<Beta[]>,
currentUserAccountID: number,
) {
const reportID = getReportIDFromLink(url);

Expand All @@ -470,8 +469,7 @@ function openReportFromDeepLink(
parentSpan: getSpan(CONST.TELEMETRY.SPAN_BOOTSPLASH.PUBLIC_ROOM_CHECK),
});

// Call the OpenReport command to check in the server if it's a public room. If so, we'll open it as an anonymous user
openReport({reportID, introSelected, parentReportActionID: '0', isFromDeepLink: true, betas, hasReportActions: false});
openReport({reportID, introSelected, parentReportActionID: '0', isFromDeepLink: true, betas, hasReportActions: false, currentUserAccountID});

// Show the sign-in page if the app is offline
if (getIsOffline()) {
Expand Down
21 changes: 17 additions & 4 deletions src/libs/actions/replaceOptimisticReportWithActualReport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Navigation, {navigationRef} from '@libs/Navigation/Navigation';
import TransitionTracker from '@libs/Navigation/TransitionTracker';
import {isMoneyRequest, isMoneyRequestReport, isOneTransactionReport} from '@libs/ReportUtils';

import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Route} from '@src/ROUTES';
import ROUTES from '@src/ROUTES';
Expand Down Expand Up @@ -47,6 +48,14 @@ Onyx.connectWithoutView({

let allReports: OnyxCollection<Report>;

let sessionAccountID: number | undefined;
Onyx.connectWithoutView({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a comment explaining why using connectWithoutView is ok in this case

key: ONYXKEYS.SESSION,
callback: (value) => {
sessionAccountID = value?.accountID;
},
});

const allReportActions: OnyxCollection<ReportActions> = {};
// Report actions are cached only to resolve parent actions for IOU cleanup; no UI subscribes, so connectWithoutView() is used.
Onyx.connectWithoutView({
Expand All @@ -60,7 +69,7 @@ Onyx.connectWithoutView({
},
});

function replaceOptimisticReportWithActualReport(report: Report, draftReportComment: string | undefined) {
function replaceOptimisticReportWithActualReport(report: Report, draftReportComment: string | undefined, currentUserAccountID: number) {
const {reportID, preexistingReportID, parentReportID, parentReportActionID} = report;

if (!reportID || !preexistingReportID) {
Expand Down Expand Up @@ -215,7 +224,7 @@ function replaceOptimisticReportWithActualReport(report: Report, draftReportComm
// betas is safe to pass as undefined because introSelected is undefined, so the code path
// that uses betas is never reached. Passing it explicitly so the compiler flags this when
// betas becomes required. Refactor issue: https://github.com/Expensify/App/issues/66424
openReport({reportID: parentReportID, introSelected: undefined, betas: undefined, hasReportActions});
openReport({reportID: parentReportID, introSelected: undefined, betas: undefined, hasReportActions, currentUserAccountID});
});
} else {
callback();
Expand All @@ -224,7 +233,7 @@ function replaceOptimisticReportWithActualReport(report: Report, draftReportComm
// betas is safe to pass as undefined because introSelected is undefined, so the code path
// that uses betas is never reached. Passing it explicitly so the compiler flags this when
// betas becomes required. Refactor issue: https://github.com/Expensify/App/issues/66424
openReport({reportID: parentReportID, introSelected: undefined, betas: undefined, hasReportActions});
openReport({reportID: parentReportID, introSelected: undefined, betas: undefined, hasReportActions, currentUserAccountID});
}
return;
}
Expand Down Expand Up @@ -257,7 +266,11 @@ Onyx.connectWithoutView({
continue;
}

replaceOptimisticReportWithActualReport(report, allReportDraftComments?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${report.reportID}`]);
replaceOptimisticReportWithActualReport(
report,
allReportDraftComments?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${report.reportID}`],
sessionAccountID ?? CONST.DEFAULT_NUMBER_ID,
);
}
},
});
Expand Down
2 changes: 1 addition & 1 deletion src/pages/Search/SearchMoneyRequestReportPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ function SearchMoneyRequestReportPage({route}: SearchMoneyRequestPageProps) {
return;
}

openReport({reportID: reportIDFromRoute, introSelected, betas, hasReportActions});
openReport({reportID: reportIDFromRoute, introSelected, betas, hasReportActions, currentUserAccountID});
isInitialMountRef.current = false;

// oneTransactionID dependency handles the case when deleting a transaction:
Expand Down
1 change: 1 addition & 0 deletions src/pages/Share/ShareDetailsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ function ShareDetailsPage({route}: ShareDetailsPageProps) {
newReportObject: report,
betas,
hasReportActions: false,
currentUserAccountID: personalDetail.accountID,
});
}
if (report.reportID) {
Expand Down
4 changes: 2 additions & 2 deletions src/pages/TransactionDuplicate/Review.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,8 @@ function TransactionDuplicateReview() {
if (!route.params.threadReportID || report?.reportID) {
return;
}
openReport({reportID: route.params.threadReportID, introSelected, betas, hasReportActions});
}, [report?.reportID, route.params.threadReportID, introSelected, betas, hasReportActions]);
openReport({reportID: route.params.threadReportID, introSelected, betas, hasReportActions, currentUserAccountID: currentPersonalDetails.accountID});
}, [report?.reportID, route.params.threadReportID, introSelected, betas, hasReportActions, currentPersonalDetails.accountID]);

useEffect(() => {
if (!transactionID) {
Expand Down
4 changes: 2 additions & 2 deletions src/pages/inbox/report/ContextMenu/ContextMenuActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -607,11 +607,11 @@ const ContextMenuActions: ContextMenuAction[] = [
(canEditReportAction(reportAction, iouTransaction) || canEditReportAction(moneyRequestAction, iouTransaction)) &&
!isArchivedRoom &&
!isChronosReport,
onPress: (closePopover, {reportID, originalReportID, reportAction, moneyRequestAction, introSelected, betas, childReportActions}) => {
onPress: (closePopover, {reportID, originalReportID, reportAction, moneyRequestAction, introSelected, betas, childReportActions, currentUserAccountID}) => {
if (isMoneyRequestAction(reportAction) || isMoneyRequestAction(moneyRequestAction)) {
const editExpense = () => {
const childReportID = reportAction?.childReportID;
openReport({reportID: childReportID, introSelected, betas, hasReportActions: !!childReportActions});
openReport({reportID: childReportID, introSelected, betas, hasReportActions: !!childReportActions, currentUserAccountID});
Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(childReportID));
};
if (closePopover) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';

import {openReport} from '@libs/actions/Report';
import {createBackupTransaction, removeBackupTransaction, restoreOriginalTransactionFromBackup} from '@libs/actions/TransactionEdit';
import {hasRoute} from '@libs/TransactionUtils';
Expand Down Expand Up @@ -32,6 +34,7 @@ type UseDistanceTransactionBackupParams = {
};

function useDistanceTransactionBackup({transaction, isCreatingNewRequest, isEditingSplit, isDraft, introSelected, betas, transactionWasSavedRef}: UseDistanceTransactionBackupParams): void {
const {accountID: currentUserAccountID} = useCurrentUserPersonalDetails();
// This effect runs when the component is mounted and unmounted. It's purpose is to be able to properly
// discard changes if the user cancels out of making any changes. This is accomplished by backing up the
// original transaction, letting the user modify the current transaction, and then if the user ever
Expand All @@ -57,7 +60,7 @@ function useDistanceTransactionBackup({transaction, isCreatingNewRequest, isEdit
if (!transaction?.reportID || hasRoute(transaction, true)) {
return;
}
openReport({reportID: transaction?.reportID, introSelected, betas, hasReportActions: true});
openReport({reportID: transaction?.reportID, introSelected, betas, hasReportActions: true, currentUserAccountID});
};
// eslint-disable-next-line react-hooks/exhaustive-deps -- mount/unmount-only effect: backup on mount, restore-or-drop on unmount, never re-runs
}, []);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator';

import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useOnyx from '@hooks/useOnyx';
import useReportIsArchived from '@hooks/useReportIsArchived';

Expand Down Expand Up @@ -93,6 +94,7 @@ function WithWritableReportOrNotFoundImpl<TProps extends WithWritableReportOrNot
const [reportDraft] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_DRAFT}${route.params.reportID}`);
const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED);
const [betas] = useOnyx(ONYXKEYS.BETAS);
const {accountID: currentUserAccountID} = useCurrentUserPersonalDetails();
const isReportArchived = useReportIsArchived(report?.reportID);

const iouTypeParamIsInvalid = !Object.values(CONST.IOU.TYPE)
Expand All @@ -104,7 +106,7 @@ function WithWritableReportOrNotFoundImpl<TProps extends WithWritableReportOrNot
if (!!report?.reportID || !route.params.reportID || !!reportDraft || !isEditing) {
return;
}
openReport({reportID: route.params.reportID, introSelected, betas});
openReport({reportID: route.params.reportID, introSelected, betas, currentUserAccountID});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid sending the default account ID

On cold starts or deep links into this edit flow, useCurrentUserPersonalDetails() can still be its provider default (accountID: CONST.DEFAULT_NUMBER_ID, i.e. 0) while session data is hydrating. Because this effect intentionally runs only once, the newly added argument can pass 0 to openReport and never retry with the real account ID; downstream whisper visibility uses a provided account ID as authoritative, so the deprecated fallback cannot recover and guided-setup/last-visible-action data can be computed for the wrong user. Please wait for a hydrated account ID or pass undefined instead of the default value.

Useful? React with 👍 / 👎.

@truph01 truph01 Jul 21, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no chance for this one to be appeared

// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ function TransactionReceiptModalContent({navigation, route}: AttachmentModalScre
if ((!!report && !!transaction) || isDraftTransaction) {
return;
}
openReport({reportID, introSelected, betas, hasReportActions});
openReport({reportID, introSelected, betas, hasReportActions, currentUserAccountID: session?.accountID});
// I'm disabling the warning, as it expects to use exhaustive deps, even though we want this useEffect to run only on the first render.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useNetwork from '@hooks/useNetwork';
import useOnyx from '@hooks/useOnyx';
import useReportIsArchived from '@hooks/useReportIsArchived';
Expand Down Expand Up @@ -48,6 +49,7 @@ function ReportAddAttachmentModalContent({route, navigation}: AttachmentModalScr
const [reportLoadingState] = useOnyx(`${ONYXKEYS.COLLECTION.RAM_ONLY_REPORT_LOADING_STATE}${reportID}`);
const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED);
const [betas] = useOnyx(ONYXKEYS.BETAS);
const {accountID: currentUserAccountID} = useCurrentUserPersonalDetails();
const isReportArchived = useReportIsArchived(reportID);
const canPerformWriteAction = canUserPerformWriteAction(report, isReportArchived);
const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP);
Expand All @@ -65,8 +67,8 @@ function ReportAddAttachmentModalContent({route, navigation}: AttachmentModalScr
}, [reportActions, reportActionID]);

const fetchReport = useCallback(() => {
openReport({reportID, introSelected, reportActionID, betas, hasReportActions});
}, [reportID, introSelected, reportActionID, betas, hasReportActions]);
openReport({reportID, introSelected, reportActionID, betas, hasReportActions, currentUserAccountID});
}, [reportID, introSelected, reportActionID, betas, hasReportActions, currentUserAccountID]);

// Close the modal if user loses write access (e.g., admin switches "Who can post" to Admins only)
useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type {Attachment} from '@components/Attachments/types';

import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useNetwork from '@hooks/useNetwork';
import useOnyx from '@hooks/useOnyx';
import useOriginalReportID from '@hooks/useOriginalReportID';
Expand Down Expand Up @@ -48,6 +49,7 @@ function ReportAttachmentModalContent({route, navigation}: AttachmentModalScreen
const hasReportActions = !!reportActions;
const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED);
const [betas] = useOnyx(ONYXKEYS.BETAS);
const {accountID: currentUserAccountID} = useCurrentUserPersonalDetails();

const originalReportID = useOriginalReportID(reportID, reportActionID ? (reportActions?.[reportActionID ?? CONST.DEFAULT_NUMBER_ID] ?? {reportActionID}) : undefined);
const reportActionReportID = originalReportID ?? reportID;
Expand Down Expand Up @@ -75,8 +77,8 @@ function ReportAttachmentModalContent({route, navigation}: AttachmentModalScreen
return;
}

openReport({reportID: reportActionReportID, introSelected, reportActionID, betas, hasReportActions});
}, [reportActionReportID, shouldFetchReport, introSelected, reportActionID, betas, hasReportActions]);
openReport({reportID: reportActionReportID, introSelected, reportActionID, betas, hasReportActions, currentUserAccountID});
}, [reportActionReportID, shouldFetchReport, introSelected, reportActionID, betas, hasReportActions, currentUserAccountID]);

const onCarouselAttachmentChange = (attachment: Attachment) => {
const routeToNavigate = ROUTES.REPORT_ATTACHMENTS.getRoute({
Expand Down
6 changes: 6 additions & 0 deletions tests/actions/IOUTest/DeleteMoneyRequestTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,7 @@ describe('actions/IOU/DeleteMoneyRequest', () => {
personalDetails: allPersonalDetails,
newReportObject: thread,
parentReportActionID: createIOUAction?.reportActionID,
currentUserAccountID: RORY_ACCOUNT_ID,
});
await waitForBatchedUpdates();

Expand Down Expand Up @@ -658,6 +659,7 @@ describe('actions/IOU/DeleteMoneyRequest', () => {
personalDetails: allPersonalDetails,
newReportObject: thread,
parentReportActionID: createIOUAction?.reportActionID,
currentUserAccountID: RORY_ACCOUNT_ID,
});
await waitForBatchedUpdates();

Expand Down Expand Up @@ -794,6 +796,7 @@ describe('actions/IOU/DeleteMoneyRequest', () => {
personalDetails: allPersonalDetails,
newReportObject: thread,
parentReportActionID: createIOUAction?.reportActionID,
currentUserAccountID: RORY_ACCOUNT_ID,
});
await waitForBatchedUpdates();

Expand Down Expand Up @@ -942,6 +945,7 @@ describe('actions/IOU/DeleteMoneyRequest', () => {
personalDetails: allPersonalDetails,
newReportObject: thread,
parentReportActionID: createIOUAction?.reportActionID,
currentUserAccountID: RORY_ACCOUNT_ID,
});

await waitForBatchedUpdates();
Expand Down Expand Up @@ -1265,6 +1269,7 @@ describe('actions/IOU/DeleteMoneyRequest', () => {
personalDetails: allPersonalDetails,
newReportObject: thread,
parentReportActionID: createIOUAction?.reportActionID,
currentUserAccountID: RORY_ACCOUNT_ID,
});
await waitForBatchedUpdates();

Expand Down Expand Up @@ -1449,6 +1454,7 @@ describe('actions/IOU/DeleteMoneyRequest', () => {
personalDetails: allPersonalDetails,
newReportObject: thread,
parentReportActionID: createIOUAction?.reportActionID,
currentUserAccountID: RORY_ACCOUNT_ID,
});
await waitForBatchedUpdates();

Expand Down
2 changes: 2 additions & 0 deletions tests/actions/IOUTest/DuplicateTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,7 @@ describe('actions/Duplicate', () => {
betas: undefined,
newReportObject: transactionThreadReport1,
parentReportActionID: iouAction1?.reportActionID,
currentUserAccountID: RORY_ACCOUNT_ID,
});
openReport({
hasReportActions: true,
Expand All @@ -487,6 +488,7 @@ describe('actions/Duplicate', () => {
betas: undefined,
newReportObject: transactionThreadReport1,
parentReportActionID: iouAction2?.reportActionID,
currentUserAccountID: RORY_ACCOUNT_ID,
});
await waitForBatchedUpdates();

Expand Down
Loading
Loading