diff --git a/src/CONST/index.ts b/src/CONST/index.ts index 11d8c46b711a..908cf759bce9 100644 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -1901,6 +1901,7 @@ const CONST = { SHOW_HOVER_PREVIEW_DELAY: 270, SHOW_HOVER_PREVIEW_ANIMATION_DURATION: 250, ACTIVITY_INDICATOR_TIMEOUT: 10000, + GET_INITIAL_URL_TIMEOUT: 10000, MIN_SMOOTH_SCROLL_EVENT_THROTTLE: 16, }, DEFERRED_LAYOUT_WRITE_KEYS: { diff --git a/src/DeepLinkHandler.tsx b/src/DeepLinkHandler.tsx index 2f50bf068148..25f214661d0b 100644 --- a/src/DeepLinkHandler.tsx +++ b/src/DeepLinkHandler.tsx @@ -26,6 +26,7 @@ type DeepLinkHandlerProps = { */ function DeepLinkHandler({onInitialUrl}: DeepLinkHandlerProps) { const linkingChangeListener = useRef(null); + const initialUrlProcessed = useRef(false); const [allReports, allReportsMetadata] = useOnyx(ONYXKEYS.COLLECTION.REPORT); const [, sessionMetadata] = useOnyx(ONYXKEYS.SESSION); @@ -39,24 +40,58 @@ function DeepLinkHandler({onInitialUrl}: DeepLinkHandlerProps) { if (isLoadingOnyxValue(allReportsMetadata, sessionMetadata, conciergeReportIDMetadata, introSelectedMetadata, isSelfTourViewedMetadata, betasMetadata)) { return; } - // If the app is opened from a deep link, get the reportID (if exists) from the deep link and navigate to the chat report - Linking.getInitialURL().then((url) => { - onInitialUrl(url as Route); - if (url) { - if (conciergeReportID === undefined) { - Log.info('[Deep link] conciergeReportID is undefined when processing initial URL', false, {url}); + // Guard against stale closures: when deps change and the effect re-runs, the previous + // getInitialURL() promise may still be in-flight. Without this guard, its .then() would + // fire with stale conciergeReportID/introSelected values, causing a duplicate + // openReportFromDeepLink() call. + let cancelled = false; + let timeoutId: ReturnType; + + // If the app is opened from a deep link, get the reportID (if exists) from the deep link and navigate to the chat report. + // We race against a timeout to prevent permanently blocking NavigationRoot if getInitialURL() never resolves + // (e.g. in HybridApp when OldDot fails to send the URL via native bridge). + Promise.race([ + Linking.getInitialURL(), + new Promise((resolve) => { + timeoutId = setTimeout(() => resolve(null), CONST.TIMING.GET_INITIAL_URL_TIMEOUT); + }), + ]) + .then((url) => { + if (cancelled) { + return; } - if (introSelected === undefined) { - Log.info('[Deep link] introSelected is undefined when processing initial URL', false, {url}); + + initialUrlProcessed.current = true; + onInitialUrl(url as Route); + + if (url) { + if (conciergeReportID === undefined) { + Log.info('[Deep link] conciergeReportID is undefined when processing initial URL', false, {url}); + } + if (introSelected === undefined) { + Log.info('[Deep link] introSelected is undefined when processing initial URL', false, {url}); + } + // Use hasAuthToken() for the latest auth state at call time, since the isAuthenticated + // closure value may be stale on cold start (useOnyx reports 'loaded' before storage completes). + const isCurrentlyAuthenticated = hasAuthToken(); + openReportFromDeepLink(url, allReports, isCurrentlyAuthenticated, conciergeReportID, introSelected, isSelfTourViewed, betas); + } else { + Report.doneCheckingPublicRoom(); } - openReportFromDeepLink(url, allReports, isAuthenticated, conciergeReportID, introSelected, isSelfTourViewed, betas); - } else { - Report.doneCheckingPublicRoom(); - } - endSpan(CONST.TELEMETRY.SPAN_BOOTSPLASH.DEEP_LINK); - }); + endSpan(CONST.TELEMETRY.SPAN_BOOTSPLASH.DEEP_LINK); + }) + .catch(() => { + if (cancelled) { + return; + } + + initialUrlProcessed.current = true; + onInitialUrl(null); + Report.doneCheckingPublicRoom(); + endSpan(CONST.TELEMETRY.SPAN_BOOTSPLASH.DEEP_LINK); + }); // Open chat report from a deep link (only mobile native) linkingChangeListener.current = Linking.addEventListener('url', (state) => { @@ -71,6 +106,8 @@ function DeepLinkHandler({onInitialUrl}: DeepLinkHandlerProps) { }); return () => { + cancelled = true; + clearTimeout(timeoutId); linkingChangeListener.current?.remove(); }; // eslint-disable-next-line react-hooks/exhaustive-deps -- intentionally excluding allReports, isAuthenticated, and onInitialUrl to avoid re-triggering deep link handling on every report update @@ -86,6 +123,19 @@ function DeepLinkHandler({onInitialUrl}: DeepLinkHandlerProps) { betasMetadata.status, ]); + // Safety net: if getInitialURL() resolves before the session loads, hasAuthToken() may return false + // for an authenticated user, causing openReportFromDeepLink to take the wrong path. Once isAuthenticated + // settles to true, unblock the UI. The initialUrlProcessed guard ensures this doesn't fire before URL + // resolution. In the common case (isAuthenticated settles first), this is a no-op because + // openReportFromDeepLink's own doneCheckingPublicRoom() call handles it. + useEffect(() => { + if (!isAuthenticated || !initialUrlProcessed.current) { + return; + } + + Report.doneCheckingPublicRoom(); + }, [isAuthenticated]); + return null; } diff --git a/src/Expensify.tsx b/src/Expensify.tsx index 70f8a81666f6..93167ef38c21 100644 --- a/src/Expensify.tsx +++ b/src/Expensify.tsx @@ -57,7 +57,7 @@ function Expensify() { const [hasAttemptedToOpenPublicRoom, setAttemptedToOpenPublicRoom] = useState(false); const {preferredLocale} = useLocalize(); const [lastRoute] = useOnyx(ONYXKEYS.LAST_ROUTE); - const [isCheckingPublicRoom = true] = useOnyx(ONYXKEYS.IS_CHECKING_PUBLIC_ROOM, {initWithStoredValues: false}); + const [isCheckingPublicRoom = true] = useOnyx(ONYXKEYS.RAM_ONLY_IS_CHECKING_PUBLIC_ROOM); const [updateRequired] = useOnyx(ONYXKEYS.RAM_ONLY_UPDATE_REQUIRED); const [lastVisitedPath] = useOnyx(ONYXKEYS.LAST_VISITED_PATH); @@ -72,7 +72,7 @@ function Expensify() { const bootsplashSpan = useRef(null); - const [initialUrl, setInitialUrl] = useState(null); + const [initialUrl, setInitialUrl] = useState(undefined); const {setIsAuthenticatedAtStartup} = useInitialURLActions(); useEffect(() => { @@ -278,12 +278,15 @@ function Expensify() { - {hasAttemptedToOpenPublicRoom && ( + {/* Wait for the initial URL to resolve before mounting NavigationRoot, because its initialState + is computed once on mount. In HybridApp, getInitialURL() may never resolve (OldDot native + bridge), so we skip this guard to avoid blocking the app. */} + {hasAttemptedToOpenPublicRoom && (CONFIG.IS_HYBRID_APP || initialUrl !== undefined) && ( )} {(isSplashVisible || isSplashReadyToBeHidden) && ( diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 3d21a3c0529a..9b0747e10395 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -318,7 +318,7 @@ const ONYXKEYS = { IS_PLAID_DISABLED: 'isPlaidDisabled', /** Token needed to initialize Plaid link */ - PLAID_LINK_TOKEN: 'plaidLinkToken', + RAM_ONLY_PLAID_LINK_TOKEN: 'plaidLinkToken', /** Capture Plaid event */ PLAID_CURRENT_EVENT: 'plaidCurrentEvent', @@ -424,7 +424,7 @@ const ONYXKEYS = { IS_BETA: 'isBeta', /** Whether we're checking if the room is public or not */ - IS_CHECKING_PUBLIC_ROOM: 'isCheckingPublicRoom', + RAM_ONLY_IS_CHECKING_PUBLIC_ROOM: 'isCheckingPublicRoom', /** A map of the user's security group IDs they belong to in specific domains */ MY_DOMAIN_SECURITY_GROUPS: 'myDomainSecurityGroups', @@ -808,7 +808,7 @@ const ONYXKEYS = { NVP_EXPENSIFY_REPORT_PDF_FILENAME: 'nvp_expensify_report_PDFFilename_', /** Stores the information about the state of issuing a new card */ - ISSUE_NEW_EXPENSIFY_CARD: 'issueNewExpensifyCard_', + RAM_ONLY_ISSUE_NEW_EXPENSIFY_CARD: 'issueNewExpensifyCard_', /** Stored the user information with whom bank account is being shared */ BANK_ACCOUNT_SHARE_DETAILS: 'expensify_bankAccountShare_', @@ -1297,7 +1297,7 @@ type OnyxCollectionValuesMapping = { [ONYXKEYS.COLLECTION.LAST_SELECTED_FEED]: OnyxTypes.CompanyCardFeedWithDomainID; [ONYXKEYS.COLLECTION.LAST_SELECTED_EXPENSIFY_CARD_FEED]: OnyxTypes.FundID; [ONYXKEYS.COLLECTION.NVP_EXPENSIFY_ON_CARD_WAITLIST]: OnyxTypes.CardOnWaitlist; - [ONYXKEYS.COLLECTION.ISSUE_NEW_EXPENSIFY_CARD]: OnyxTypes.IssueNewCard; + [ONYXKEYS.COLLECTION.RAM_ONLY_ISSUE_NEW_EXPENSIFY_CARD]: OnyxTypes.IssueNewCard; [ONYXKEYS.COLLECTION.SAML_METADATA]: OnyxTypes.SamlMetadata; [ONYXKEYS.COLLECTION.DOMAIN_PENDING_ACTIONS]: OnyxTypes.DomainPendingActions; [ONYXKEYS.COLLECTION.DOMAIN_ERRORS]: OnyxTypes.DomainErrors; @@ -1383,7 +1383,7 @@ type OnyxValuesMapping = { [ONYXKEYS.NVP_SEEN_NEW_USER_MODAL]: boolean; [ONYXKEYS.PLAID_DATA]: OnyxTypes.PlaidData; [ONYXKEYS.IS_PLAID_DISABLED]: boolean; - [ONYXKEYS.PLAID_LINK_TOKEN]: string; + [ONYXKEYS.RAM_ONLY_PLAID_LINK_TOKEN]: string; [ONYXKEYS.ONFIDO_TOKEN]: string; [ONYXKEYS.ONFIDO_APPLICANT_ID]: string; [ONYXKEYS.NVP_PREFERRED_LOCALE]: OnyxTypes.Locale; @@ -1433,7 +1433,7 @@ type OnyxValuesMapping = { [ONYXKEYS.LAST_ACCESSED_WORKSPACE_POLICY_ID]: string; [ONYXKEYS.SHOULD_SHOW_COMPOSE_INPUT]: boolean; [ONYXKEYS.IS_BETA]: boolean; - [ONYXKEYS.IS_CHECKING_PUBLIC_ROOM]: boolean; + [ONYXKEYS.RAM_ONLY_IS_CHECKING_PUBLIC_ROOM]: boolean; [ONYXKEYS.MY_DOMAIN_SECURITY_GROUPS]: Record; [ONYXKEYS.DOMAIN_MEMBERS_SELECTED_FOR_MOVE]: string[]; [ONYXKEYS.VERIFY_3DS_SUBSCRIPTION]: string; diff --git a/src/components/AddPlaidBankAccount.tsx b/src/components/AddPlaidBankAccount.tsx index c68624505d5a..626e804f4bd0 100644 --- a/src/components/AddPlaidBankAccount.tsx +++ b/src/components/AddPlaidBankAccount.tsx @@ -83,7 +83,7 @@ function AddPlaidBankAccount({ const subscribedKeyboardShortcuts = useRef void>>([]); const previousNetworkState = useRef(undefined); const [selectedPlaidAccountMask, setSelectedPlaidAccountMask] = useState(defaultSelectedPlaidAccountMask); - const [plaidLinkToken] = useOnyx(ONYXKEYS.PLAID_LINK_TOKEN, {initWithStoredValues: false}); + const [plaidLinkToken] = useOnyx(ONYXKEYS.RAM_ONLY_PLAID_LINK_TOKEN); const [isPlaidDisabled] = useOnyx(ONYXKEYS.IS_PLAID_DISABLED); const {translate} = useLocalize(); const {isOffline} = useNetwork(); diff --git a/src/libs/ExportOnyxState/common.ts b/src/libs/ExportOnyxState/common.ts index fc33285384e1..542a7f75a8ba 100644 --- a/src/libs/ExportOnyxState/common.ts +++ b/src/libs/ExportOnyxState/common.ts @@ -98,7 +98,7 @@ const onyxKeysToRemove = new Set | ValueOf { - Onyx.set(ONYXKEYS.PLAID_LINK_TOKEN, ''); + Onyx.set(ONYXKEYS.RAM_ONLY_PLAID_LINK_TOKEN, ''); Onyx.set(ONYXKEYS.PLAID_CURRENT_EVENT, null); return Onyx.set(ONYXKEYS.PLAID_DATA, CONST.PLAID.DEFAULT_DATA); } diff --git a/src/libs/actions/Card.ts b/src/libs/actions/Card.ts index 34974db0d991..c6fc1a6b48d9 100644 --- a/src/libs/actions/Card.ts +++ b/src/libs/actions/Card.ts @@ -748,7 +748,7 @@ function getCardDefaultName(userName?: string) { } function setIssueNewCardStepAndData({data, isEditing, step, policyID, isChangeAssigneeDisabled}: IssueNewCardFlowData) { - Onyx.merge(`${ONYXKEYS.COLLECTION.ISSUE_NEW_EXPENSIFY_CARD}${policyID}`, { + Onyx.merge(`${ONYXKEYS.COLLECTION.RAM_ONLY_ISSUE_NEW_EXPENSIFY_CARD}${policyID}`, { data, isEditing, currentStep: step, @@ -764,7 +764,7 @@ function setDraftInviteAccountID(assigneeEmail: string | undefined, assigneeAcco } function clearIssueNewCardFlow(policyID: string | undefined) { - Onyx.set(`${ONYXKEYS.COLLECTION.ISSUE_NEW_EXPENSIFY_CARD}${policyID}`, { + Onyx.set(`${ONYXKEYS.COLLECTION.RAM_ONLY_ISSUE_NEW_EXPENSIFY_CARD}${policyID}`, { currentStep: null, data: {}, isSuccessful: false, @@ -777,7 +777,7 @@ function clearIssueNewCardFormData() { } function clearIssueNewCardError(policyID: string | undefined) { - Onyx.merge(`${ONYXKEYS.COLLECTION.ISSUE_NEW_EXPENSIFY_CARD}${policyID}`, {errors: null}); + Onyx.merge(`${ONYXKEYS.COLLECTION.RAM_ONLY_ISSUE_NEW_EXPENSIFY_CARD}${policyID}`, {errors: null}); } function buildCardListUpdates(workspaceAccountID: number, cardID: number, cardUpdateData: CardListUpdateData, shouldUpdateCardList: boolean): CardOnyxUpdate[] { @@ -1367,10 +1367,10 @@ function issueExpensifyCard( const {assigneeEmail, limit, limitType, cardTitle, cardType, validFrom, validThru} = data; - const optimisticData: Array> = [ + const optimisticData: Array> = [ { onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.ISSUE_NEW_EXPENSIFY_CARD}${policyID}`, + key: `${ONYXKEYS.COLLECTION.RAM_ONLY_ISSUE_NEW_EXPENSIFY_CARD}${policyID}`, value: { isLoading: true, errors: null, @@ -1379,10 +1379,10 @@ function issueExpensifyCard( }, ]; - const successData: Array> = [ + const successData: Array> = [ { onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.ISSUE_NEW_EXPENSIFY_CARD}${policyID}`, + key: `${ONYXKEYS.COLLECTION.RAM_ONLY_ISSUE_NEW_EXPENSIFY_CARD}${policyID}`, value: { isLoading: false, isSuccessful: true, @@ -1390,10 +1390,10 @@ function issueExpensifyCard( }, ]; - const failureData: Array> = [ + const failureData: Array> = [ { onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.ISSUE_NEW_EXPENSIFY_CARD}${policyID}`, + key: `${ONYXKEYS.COLLECTION.RAM_ONLY_ISSUE_NEW_EXPENSIFY_CARD}${policyID}`, value: { isLoading: false, errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage'), diff --git a/src/libs/actions/Plaid.ts b/src/libs/actions/Plaid.ts index d7f44c40df62..eb0d6f59ff54 100644 --- a/src/libs/actions/Plaid.ts +++ b/src/libs/actions/Plaid.ts @@ -31,7 +31,7 @@ function openPlaidBankLogin(allowDebit: boolean, bankAccountID: number) { }, { onyxMethod: Onyx.METHOD.SET, - key: ONYXKEYS.PLAID_LINK_TOKEN, + key: ONYXKEYS.RAM_ONLY_PLAID_LINK_TOKEN, value: '', }, { @@ -43,7 +43,17 @@ function openPlaidBankLogin(allowDebit: boolean, bankAccountID: number) { }, ]; - API.read(READ_COMMANDS.OPEN_PLAID_BANK_LOGIN, params, {optimisticData}); + const failureData = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.PLAID_DATA, + value: { + isLoading: false, + }, + }, + ]; + + API.read(READ_COMMANDS.OPEN_PLAID_BANK_LOGIN, params, {optimisticData, failureData}); } /** @@ -69,12 +79,22 @@ function openPlaidCompanyCardLogin(country: string, domain?: string, feed?: Card }, { onyxMethod: Onyx.METHOD.SET, - key: ONYXKEYS.PLAID_LINK_TOKEN, + key: ONYXKEYS.RAM_ONLY_PLAID_LINK_TOKEN, value: '', }, ]; - API.read(READ_COMMANDS.OPEN_PLAID_CARDS_BANK_LOGIN, params, {optimisticData}); + const failureData = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.PLAID_DATA, + value: { + isLoading: false, + }, + }, + ]; + + API.read(READ_COMMANDS.OPEN_PLAID_CARDS_BANK_LOGIN, params, {optimisticData, failureData}); } function openPlaidBankAccountSelector(publicToken: string, bankName: string, allowDebit: boolean, bankAccountID: number) { diff --git a/src/libs/actions/QueuedOnyxUpdates.ts b/src/libs/actions/QueuedOnyxUpdates.ts index 62ce86816243..1c4bee516c69 100644 --- a/src/libs/actions/QueuedOnyxUpdates.ts +++ b/src/libs/actions/QueuedOnyxUpdates.ts @@ -45,7 +45,7 @@ function flushQueue(): Promise { ONYXKEYS.CREDENTIALS, ONYXKEYS.RAM_ONLY_IS_SIDEBAR_LOADED, ONYXKEYS.ACCOUNT, - ONYXKEYS.IS_CHECKING_PUBLIC_ROOM, + ONYXKEYS.RAM_ONLY_IS_CHECKING_PUBLIC_ROOM, ONYXKEYS.MODAL, ONYXKEYS.NETWORK, ONYXKEYS.SHOULD_SHOW_COMPOSE_INPUT, diff --git a/src/libs/actions/ReimbursementAccount/resetUSDBankAccount.ts b/src/libs/actions/ReimbursementAccount/resetUSDBankAccount.ts index ece32c9c1f4b..ce33a5b144cc 100644 --- a/src/libs/actions/ReimbursementAccount/resetUSDBankAccount.ts +++ b/src/libs/actions/ReimbursementAccount/resetUSDBankAccount.ts @@ -35,7 +35,7 @@ function resetUSDBankAccount( | typeof ONYXKEYS.ONFIDO_TOKEN | typeof ONYXKEYS.ONFIDO_APPLICANT_ID | typeof ONYXKEYS.PLAID_DATA - | typeof ONYXKEYS.PLAID_LINK_TOKEN + | typeof ONYXKEYS.RAM_ONLY_PLAID_LINK_TOKEN | typeof ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT | typeof ONYXKEYS.NVP_LAST_PAYMENT_METHOD > = { @@ -76,7 +76,7 @@ function resetUSDBankAccount( }, { onyxMethod: Onyx.METHOD.SET, - key: ONYXKEYS.PLAID_LINK_TOKEN, + key: ONYXKEYS.RAM_ONLY_PLAID_LINK_TOKEN, value: '', }, { diff --git a/src/libs/actions/Report/index.ts b/src/libs/actions/Report/index.ts index dc998831809f..799ed1db9b1e 100644 --- a/src/libs/actions/Report/index.ts +++ b/src/libs/actions/Report/index.ts @@ -1388,7 +1388,7 @@ function openReport(params: OpenReportActionParams) { }); } - const finallyData: Array> = []; + const finallyData: Array> = []; const parameters: OpenReportParams = { reportID, @@ -1658,7 +1658,7 @@ function openReport(params: OpenReportActionParams) { if (isFromDeepLink) { finallyData.push({ onyxMethod: Onyx.METHOD.SET, - key: ONYXKEYS.IS_CHECKING_PUBLIC_ROOM, + key: ONYXKEYS.RAM_ONLY_IS_CHECKING_PUBLIC_ROOM, value: false, }); @@ -4407,7 +4407,7 @@ function toggleEmojiReaction( } function doneCheckingPublicRoom() { - Onyx.set(ONYXKEYS.IS_CHECKING_PUBLIC_ROOM, false); + Onyx.set(ONYXKEYS.RAM_ONLY_IS_CHECKING_PUBLIC_ROOM, false); } function navigateToMostRecentReport( diff --git a/src/libs/actions/Welcome/index.ts b/src/libs/actions/Welcome/index.ts index a10f96e62a8e..252f6a83a04e 100644 --- a/src/libs/actions/Welcome/index.ts +++ b/src/libs/actions/Welcome/index.ts @@ -16,6 +16,10 @@ import type Onboarding from '@src/types/onyx/Onboarding'; import type {OnboardingCompanySize} from './OnboardingFlow'; let isLoadingReportData = true; +// Tracks whether we've seen loading start (true) in the current session. +// Without this, a stale persisted `false` from a previous session would +// resolve the onServerDataReady() promise before OpenApp/ReconnectApp completes. +let hasStartedLoading = false; let resolveIsReadyPromise: (value?: Promise) => void | undefined; let isServerDataReadyPromise = new Promise((resolve) => { @@ -116,10 +120,16 @@ function completeHybridAppOnboarding() { // and doesn't need to trigger component re-renders. Onyx.connectWithoutView({ key: ONYXKEYS.IS_LOADING_REPORT_DATA, - initWithStoredValues: false, callback: (value) => { isLoadingReportData = value ?? false; - checkServerDataReady(); + if (isLoadingReportData) { + hasStartedLoading = true; + } + // Only resolve once loading has started — this ensures a stale + // persisted `false` from a previous session is ignored. + if (hasStartedLoading) { + checkServerDataReady(); + } }, }); @@ -128,6 +138,7 @@ function resetAllChecks() { resolveIsReadyPromise = resolve; }); isLoadingReportData = true; + hasStartedLoading = false; } function setSelfTourViewed(shouldUpdateOnyxDataOnlyLocally = false) { diff --git a/src/pages/EnablePayments/EnablePayments.tsx b/src/pages/EnablePayments/EnablePayments.tsx index 9b09b1e14e39..282fdf2e33ae 100644 --- a/src/pages/EnablePayments/EnablePayments.tsx +++ b/src/pages/EnablePayments/EnablePayments.tsx @@ -1,4 +1,4 @@ -import React, {useEffect, useRef, useState} from 'react'; +import React, {useEffect} from 'react'; import {View} from 'react-native'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; @@ -19,6 +19,7 @@ import AddBankAccount from './AddBankAccount/AddBankAccount'; import FailedKYC from './FailedKYC'; import FeesAndTerms from './FeesAndTerms/FeesAndTerms'; import PersonalInfo from './PersonalInfo/PersonalInfo'; +import useHasFreshWalletData from './useHasFreshWalletData'; import VerifyIdentity from './VerifyIdentity/VerifyIdentity'; function EnablePaymentsPage() { @@ -30,8 +31,7 @@ function EnablePaymentsPage() { const [fundList] = useOnyx(ONYXKEYS.FUND_LIST); const paymentCardList = fundList ?? {}; - const wasLoadingRef = useRef(false); - const [hasFreshData, setHasFreshData] = useState(false); + const hasFreshData = useHasFreshWalletData(isOffline, userWallet?.isLoading); useEffect(() => { if (isOffline) { @@ -41,26 +41,6 @@ function EnablePaymentsPage() { openEnablePaymentsPage(); }, [isOffline]); - // Only render step content after the fresh data loading cycle (isLoading: true → false) completes, - // to avoid acting on stale cached values from a previous session. - useEffect(() => { - if (isOffline) { - return; - } - - if (userWallet?.isLoading) { - wasLoadingRef.current = true; - return; - } - - if (!wasLoadingRef.current) { - return; - } - - // eslint-disable-next-line react-hooks/set-state-in-effect -- we need to trigger a re-render when fresh data arrives to stop showing the loading indicator - setHasFreshData(true); - }, [isOffline, userWallet?.isLoading]); - const isUserWalletEmpty = isEmptyObject(userWallet); if (isUserWalletEmpty || userWallet?.isLoading || (!hasFreshData && !isOffline)) { const reasonAttributes: SkeletonSpanReasonAttributes = { diff --git a/src/pages/EnablePayments/EnablePaymentsPage.tsx b/src/pages/EnablePayments/EnablePaymentsPage.tsx index 891c3b21e582..a54c324ada97 100644 --- a/src/pages/EnablePayments/EnablePaymentsPage.tsx +++ b/src/pages/EnablePayments/EnablePaymentsPage.tsx @@ -1,4 +1,4 @@ -import React, {useEffect, useRef, useState} from 'react'; +import React, {useEffect} from 'react'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; @@ -18,6 +18,7 @@ import FailedKYC from './FailedKYC'; // Steps import OnfidoStep from './OnfidoStep'; import TermsStep from './TermsStep'; +import useHasFreshWalletData from './useHasFreshWalletData'; function EnablePaymentsPage() { const {translate} = useLocalize(); @@ -25,8 +26,7 @@ function EnablePaymentsPage() { const [userWallet] = useOnyx(ONYXKEYS.USER_WALLET); const {isPendingOnfidoResult, hasFailedOnfido} = userWallet ?? {}; - const wasLoadingRef = useRef(false); - const [hasFreshData, setHasFreshData] = useState(false); + const hasFreshData = useHasFreshWalletData(isOffline, userWallet?.isLoading); // Always fetch fresh wallet data on mount useEffect(() => { @@ -40,27 +40,15 @@ function EnablePaymentsPage() { // Only redirect after the fresh data loading cycle (isLoading: true → false) completes, // to avoid acting on stale cached values from a previous session. useEffect(() => { - if (isOffline) { - return; - } - - if (userWallet?.isLoading) { - wasLoadingRef.current = true; + if (isOffline || !hasFreshData) { return; } - if (!wasLoadingRef.current) { - return; - } - - // eslint-disable-next-line react-hooks/set-state-in-effect -- we need to trigger a re-render when fresh data arrives to stop showing the loading indicator - setHasFreshData(true); - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing if (isPendingOnfidoResult || hasFailedOnfido) { Navigation.navigate(ROUTES.SETTINGS_WALLET, {forceReplace: true}); } - }, [isOffline, isPendingOnfidoResult, hasFailedOnfido, userWallet?.isLoading]); + }, [isOffline, isPendingOnfidoResult, hasFailedOnfido, hasFreshData]); const isUserWalletEmpty = isEmptyObject(userWallet); if (isUserWalletEmpty || userWallet?.isLoading || (!hasFreshData && !isOffline)) { diff --git a/src/pages/EnablePayments/useHasFreshWalletData.ts b/src/pages/EnablePayments/useHasFreshWalletData.ts new file mode 100644 index 000000000000..1d98c04a9b67 --- /dev/null +++ b/src/pages/EnablePayments/useHasFreshWalletData.ts @@ -0,0 +1,27 @@ +import {useState} from 'react'; + +type WalletDataState = 'idle' | 'loading' | 'fresh'; + +function computeNextState(current: WalletDataState, isOffline: boolean, isLoading: boolean | undefined): WalletDataState { + if (isOffline) { + return current; + } + if (current === 'idle' && isLoading) { + return 'loading'; + } + if (current === 'loading' && !isLoading) { + return 'fresh'; + } + return current; +} + +function useHasFreshWalletData(isOffline: boolean, isLoading: boolean | undefined): boolean { + const [state, setState] = useState('idle'); + const next = computeNextState(state, isOffline, isLoading); + if (next !== state) { + setState(next); + } + return next === 'fresh'; +} + +export default useHasFreshWalletData; diff --git a/src/pages/ReimbursementAccount/ReimbursementAccountPage.tsx b/src/pages/ReimbursementAccount/ReimbursementAccountPage.tsx index d4a0a8f6e5da..c9838eeffa0d 100644 --- a/src/pages/ReimbursementAccount/ReimbursementAccountPage.tsx +++ b/src/pages/ReimbursementAccount/ReimbursementAccountPage.tsx @@ -271,7 +271,11 @@ function ReimbursementAccountPage({route, policy, isLoadingPolicy, navigation}: setShouldShowConnectedVerifiedBankAccount(isNonUSDSetup ? achData?.state === CONST.BANK_ACCOUNT.STATE.OPEN : achData?.currentStep === CONST.BANK_ACCOUNT.STEP.ENABLE); setShouldShowContinueSetupButton(shouldShowContinueSetupButtonValue); - }, [policyIDParam, achData?.currentStep, shouldShowContinueSetupButtonValue, isNonUSDSetup, isPreviousPolicy, achData?.state, policyCurrency, USDBankAccountStep]); + // USDBankAccountStep is intentionally omitted from deps. This effect must only react to server-side + // achData changes — not to local USDBankAccountStep updates — otherwise it races with prepareNextStep + // and briefly pulls USDBankAccountStep back to the server value before the Onyx merge lands. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [policyIDParam, achData?.currentStep, shouldShowContinueSetupButtonValue, isNonUSDSetup, isPreviousPolicy, achData?.state, policyCurrency]); useEffect(() => { if (!prevPolicyCurrency || policyCurrency === prevPolicyCurrency) { diff --git a/src/pages/ReimbursementAccount/USD/BankInfo/BankInfo.tsx b/src/pages/ReimbursementAccount/USD/BankInfo/BankInfo.tsx index c590007813b8..e45bf8eedf32 100644 --- a/src/pages/ReimbursementAccount/USD/BankInfo/BankInfo.tsx +++ b/src/pages/ReimbursementAccount/USD/BankInfo/BankInfo.tsx @@ -39,7 +39,7 @@ const receivedRedirectURI = getPlaidOAuthReceivedRedirectURI(); function BankInfo({onBackButtonPress, policyID, setUSDBankAccountStep}: BankInfoProps) { const [reimbursementAccount] = useOnyx(ONYXKEYS.REIMBURSEMENT_ACCOUNT); const [reimbursementAccountDraft] = useOnyx(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT); - const [plaidLinkToken] = useOnyx(ONYXKEYS.PLAID_LINK_TOKEN); + const [plaidLinkToken] = useOnyx(ONYXKEYS.RAM_ONLY_PLAID_LINK_TOKEN); const {translate} = useLocalize(); const redirectedFromPlaidToManualRef = useRef(false); diff --git a/src/pages/media/AttachmentModalScreen/routes/WorkspaceAvatarModalContent.tsx b/src/pages/media/AttachmentModalScreen/routes/WorkspaceAvatarModalContent.tsx index 70fa08ede752..f1e2083d3cab 100644 --- a/src/pages/media/AttachmentModalScreen/routes/WorkspaceAvatarModalContent.tsx +++ b/src/pages/media/AttachmentModalScreen/routes/WorkspaceAvatarModalContent.tsx @@ -16,7 +16,7 @@ function WorkspaceAvatarModalContent({navigation, route}: AttachmentModalScreenP const defaultAvatars = useDefaultAvatars(); const policy = usePolicy(policyID); - const [isLoadingApp = false] = useOnyx(ONYXKEYS.IS_LOADING_APP, {initWithStoredValues: false}); + const [isLoadingApp = false] = useOnyx(ONYXKEYS.IS_LOADING_APP); const avatarURL = policy?.avatarURL ?? getDefaultWorkspaceAvatar(policy?.name ?? fallbackLetter); const source = getFullSizeAvatar({avatarSource: avatarURL, defaultAvatars}); diff --git a/src/pages/settings/Wallet/PersonalCards/steps/PlaidConnectionStep.tsx b/src/pages/settings/Wallet/PersonalCards/steps/PlaidConnectionStep.tsx index 18163fd85b37..bd3f32d5efd7 100644 --- a/src/pages/settings/Wallet/PersonalCards/steps/PlaidConnectionStep.tsx +++ b/src/pages/settings/Wallet/PersonalCards/steps/PlaidConnectionStep.tsx @@ -78,7 +78,7 @@ function PlaidConnectionStep({feed, onExit}: {feed?: CompanyCardFeedWithDomainID const [addNewPersonalCard] = useOnyx(ONYXKEYS.ADD_NEW_PERSONAL_CARD); const isUSCountry = addNewPersonalCard?.data?.selectedCountry === CONST.COUNTRY.US; const [isPlaidDisabled] = useOnyx(ONYXKEYS.IS_PLAID_DISABLED); - const [plaidLinkToken] = useOnyx(ONYXKEYS.PLAID_LINK_TOKEN); + const [plaidLinkToken] = useOnyx(ONYXKEYS.RAM_ONLY_PLAID_LINK_TOKEN); const [plaidData] = useOnyx(ONYXKEYS.PLAID_DATA); const plaidErrors = plaidData?.errors; const subscribedKeyboardShortcuts = useRef void>>([]); diff --git a/src/pages/workspace/companyCards/addNew/PlaidConnectionStep.tsx b/src/pages/workspace/companyCards/addNew/PlaidConnectionStep.tsx index 9f41aa1b73da..0649aad2d2ab 100644 --- a/src/pages/workspace/companyCards/addNew/PlaidConnectionStep.tsx +++ b/src/pages/workspace/companyCards/addNew/PlaidConnectionStep.tsx @@ -41,7 +41,7 @@ function PlaidConnectionStep({feed, policyID, onExit, title}: PlaidConnectionSte const [addNewCard] = useOnyx(ONYXKEYS.ADD_NEW_COMPANY_CARD); const isUSCountry = addNewCard?.data?.selectedCountry === CONST.COUNTRY.US; const [isPlaidDisabled] = useOnyx(ONYXKEYS.IS_PLAID_DISABLED); - const [plaidLinkToken] = useOnyx(ONYXKEYS.PLAID_LINK_TOKEN); + const [plaidLinkToken] = useOnyx(ONYXKEYS.RAM_ONLY_PLAID_LINK_TOKEN); const [plaidData] = useOnyx(ONYXKEYS.PLAID_DATA); const plaidErrors = plaidData?.errors; const subscribedKeyboardShortcuts = useRef void>>([]); diff --git a/src/pages/workspace/expensifyCard/issueNew/AssigneeStep.tsx b/src/pages/workspace/expensifyCard/issueNew/AssigneeStep.tsx index 815e4e82228b..8b7e055d005c 100644 --- a/src/pages/workspace/expensifyCard/issueNew/AssigneeStep.tsx +++ b/src/pages/workspace/expensifyCard/issueNew/AssigneeStep.tsx @@ -47,7 +47,7 @@ function AssigneeStep({policy, stepNames, startStepIndex, route}: AssigneeStepPr const icons = useMemoizedLazyExpensifyIcons(['FallbackAvatar']); const {isOffline} = useNetwork(); const policyID = route.params.policyID; - const [issueNewCard] = useOnyx(`${ONYXKEYS.COLLECTION.ISSUE_NEW_EXPENSIFY_CARD}${policyID}`); + const [issueNewCard] = useOnyx(`${ONYXKEYS.COLLECTION.RAM_ONLY_ISSUE_NEW_EXPENSIFY_CARD}${policyID}`); const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST); const [account] = useOnyx(ONYXKEYS.ACCOUNT); const [didScreenTransitionEnd, setDidScreenTransitionEnd] = useState(false); diff --git a/src/pages/workspace/expensifyCard/issueNew/CardNameStep.tsx b/src/pages/workspace/expensifyCard/issueNew/CardNameStep.tsx index 0848e753f410..07ce73efac1b 100644 --- a/src/pages/workspace/expensifyCard/issueNew/CardNameStep.tsx +++ b/src/pages/workspace/expensifyCard/issueNew/CardNameStep.tsx @@ -34,7 +34,7 @@ function CardNameStep({policyID, stepNames, startStepIndex}: CardNameStepProps) const {translate} = useLocalize(); const styles = useThemeStyles(); const {inputCallbackRef} = useAutoFocusInput(); - const [issueNewCard] = useOnyx(`${ONYXKEYS.COLLECTION.ISSUE_NEW_EXPENSIFY_CARD}${policyID}`); + const [issueNewCard] = useOnyx(`${ONYXKEYS.COLLECTION.RAM_ONLY_ISSUE_NEW_EXPENSIFY_CARD}${policyID}`); const isEditing = issueNewCard?.isEditing; const data = issueNewCard?.data; diff --git a/src/pages/workspace/expensifyCard/issueNew/CardTypeStep.tsx b/src/pages/workspace/expensifyCard/issueNew/CardTypeStep.tsx index c88c6844a3a9..72675705449e 100644 --- a/src/pages/workspace/expensifyCard/issueNew/CardTypeStep.tsx +++ b/src/pages/workspace/expensifyCard/issueNew/CardTypeStep.tsx @@ -33,7 +33,7 @@ function CardTypeStep({policy, stepNames, startStepIndex}: CardTypeStepProps) { const styles = useThemeStyles(); const illustrations = useMemoizedLazyIllustrations(['HandCard', 'VirtualCard']); const policyID = policy?.id; - const [issueNewCard] = useOnyx(`${ONYXKEYS.COLLECTION.ISSUE_NEW_EXPENSIFY_CARD}${policyID}`); + const [issueNewCard] = useOnyx(`${ONYXKEYS.COLLECTION.RAM_ONLY_ISSUE_NEW_EXPENSIFY_CARD}${policyID}`); const isEditing = issueNewCard?.isEditing; diff --git a/src/pages/workspace/expensifyCard/issueNew/ConfirmationStep.tsx b/src/pages/workspace/expensifyCard/issueNew/ConfirmationStep.tsx index 1a9de688ff4a..5a6c1516abd1 100644 --- a/src/pages/workspace/expensifyCard/issueNew/ConfirmationStep.tsx +++ b/src/pages/workspace/expensifyCard/issueNew/ConfirmationStep.tsx @@ -45,7 +45,7 @@ function ConfirmationStep({policyID, stepNames, startStepIndex, backTo}: Confirm const styles = useThemeStyles(); const {isOffline} = useNetwork(); const [account] = useOnyx(ONYXKEYS.ACCOUNT); - const [issueNewCard] = useOnyx(`${ONYXKEYS.COLLECTION.ISSUE_NEW_EXPENSIFY_CARD}${policyID}`); + const [issueNewCard] = useOnyx(`${ONYXKEYS.COLLECTION.RAM_ONLY_ISSUE_NEW_EXPENSIFY_CARD}${policyID}`); const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`); const defaultFundID = useDefaultFundID(policyID); const {isBetaEnabled} = usePermissions(); diff --git a/src/pages/workspace/expensifyCard/issueNew/InviteNewMemberStep.tsx b/src/pages/workspace/expensifyCard/issueNew/InviteNewMemberStep.tsx index 16aaa18f6caa..813fa306e413 100644 --- a/src/pages/workspace/expensifyCard/issueNew/InviteNewMemberStep.tsx +++ b/src/pages/workspace/expensifyCard/issueNew/InviteNewMemberStep.tsx @@ -22,7 +22,7 @@ type InviteeNewMemberStepProps = Omit { diff --git a/src/pages/workspace/expensifyCard/issueNew/IssueNewCardConfirmMagicCodePage.tsx b/src/pages/workspace/expensifyCard/issueNew/IssueNewCardConfirmMagicCodePage.tsx index b2c2e01fe5d9..242fc4626e02 100644 --- a/src/pages/workspace/expensifyCard/issueNew/IssueNewCardConfirmMagicCodePage.tsx +++ b/src/pages/workspace/expensifyCard/issueNew/IssueNewCardConfirmMagicCodePage.tsx @@ -26,7 +26,7 @@ function IssueNewCardConfirmMagicCodePage({route}: IssueNewCardConfirmMagicCodeP const [account] = useOnyx(ONYXKEYS.ACCOUNT); const [session] = useOnyx(ONYXKEYS.SESSION); const primaryLogin = account?.primaryLogin ?? session?.email ?? ''; - const [issueNewCard] = useOnyx(`${ONYXKEYS.COLLECTION.ISSUE_NEW_EXPENSIFY_CARD}${policyID}`); + const [issueNewCard] = useOnyx(`${ONYXKEYS.COLLECTION.RAM_ONLY_ISSUE_NEW_EXPENSIFY_CARD}${policyID}`); const validateError = getLatestErrorMessageField(issueNewCard); const data = issueNewCard?.data; const isSuccessful = issueNewCard?.isSuccessful; diff --git a/src/pages/workspace/expensifyCard/issueNew/IssueNewCardPage.tsx b/src/pages/workspace/expensifyCard/issueNew/IssueNewCardPage.tsx index ac6bd562bfbe..cc39f988df40 100644 --- a/src/pages/workspace/expensifyCard/issueNew/IssueNewCardPage.tsx +++ b/src/pages/workspace/expensifyCard/issueNew/IssueNewCardPage.tsx @@ -46,7 +46,7 @@ function getStartStepIndex(issueNewCard: OnyxEntry): number { function IssueNewCardPage({policy, route}: IssueNewCardPageProps) { const policyID = policy?.id; - const [issueNewCard] = useOnyx(`${ONYXKEYS.COLLECTION.ISSUE_NEW_EXPENSIFY_CARD}${policyID}`, {initWithStoredValues: false}); + const [issueNewCard] = useOnyx(`${ONYXKEYS.COLLECTION.RAM_ONLY_ISSUE_NEW_EXPENSIFY_CARD}${policyID}`); const {currentStep} = issueNewCard ?? {}; const backTo = route?.params?.backTo; const {isDelegateAccessRestricted} = useDelegateNoAccessState(); diff --git a/src/pages/workspace/expensifyCard/issueNew/LimitTypeStep.tsx b/src/pages/workspace/expensifyCard/issueNew/LimitTypeStep.tsx index 941339de9921..6e634ee2a96c 100644 --- a/src/pages/workspace/expensifyCard/issueNew/LimitTypeStep.tsx +++ b/src/pages/workspace/expensifyCard/issueNew/LimitTypeStep.tsx @@ -38,7 +38,7 @@ function LimitTypeStep({policy, stepNames, startStepIndex}: LimitTypeStepProps) const {translate} = useLocalize(); const styles = useThemeStyles(); const policyID = policy?.id; - const [issueNewCard] = useOnyx(`${ONYXKEYS.COLLECTION.ISSUE_NEW_EXPENSIFY_CARD}${policyID}`); + const [issueNewCard] = useOnyx(`${ONYXKEYS.COLLECTION.RAM_ONLY_ISSUE_NEW_EXPENSIFY_CARD}${policyID}`); const formRef = useRef(null); const areApprovalsConfigured = getApprovalWorkflow(policy) !== CONST.POLICY.APPROVAL_MODE.OPTIONAL; diff --git a/src/pages/workspace/expensifyCard/issueNew/SetExpiryOptionsStep.tsx b/src/pages/workspace/expensifyCard/issueNew/SetExpiryOptionsStep.tsx index 78ad0e1bd6f6..1b49efb79427 100644 --- a/src/pages/workspace/expensifyCard/issueNew/SetExpiryOptionsStep.tsx +++ b/src/pages/workspace/expensifyCard/issueNew/SetExpiryOptionsStep.tsx @@ -33,7 +33,7 @@ function SetExpiryOptionsStep({policy, stepNames, startStepIndex}: SetExpiryOpti const {translate} = useLocalize(); const styles = useThemeStyles(); const policyID = policy?.id; - const [issueNewCard] = useOnyx(`${ONYXKEYS.COLLECTION.ISSUE_NEW_EXPENSIFY_CARD}${policyID}`); + const [issueNewCard] = useOnyx(`${ONYXKEYS.COLLECTION.RAM_ONLY_ISSUE_NEW_EXPENSIFY_CARD}${policyID}`); const personalDetails = usePersonalDetails(); const assigneePersonalDetails = Object.values(personalDetails ?? {}).find((detail) => detail?.login === issueNewCard?.data?.assigneeEmail); diff --git a/src/setup/index.ts b/src/setup/index.ts index 7dfcba9ac516..66b845990bde 100644 --- a/src/setup/index.ts +++ b/src/setup/index.ts @@ -62,10 +62,13 @@ export default function () { ONYXKEYS.RAM_ONLY_MOBILE_SELECTION_MODE, ONYXKEYS.RAM_ONLY_IS_SIDEBAR_LOADED, ONYXKEYS.DERIVED.RAM_ONLY_SORTED_REPORT_ACTIONS, + ONYXKEYS.RAM_ONLY_IS_CHECKING_PUBLIC_ROOM, ONYXKEYS.RAM_ONLY_UPDATE_AVAILABLE, ONYXKEYS.RAM_ONLY_UPDATE_REQUIRED, ONYXKEYS.RAM_ONLY_IS_SEARCHING_FOR_REPORTS, ONYXKEYS.RAM_ONLY_WALLET_ONFIDO, + ONYXKEYS.RAM_ONLY_PLAID_LINK_TOKEN, + ONYXKEYS.COLLECTION.RAM_ONLY_ISSUE_NEW_EXPENSIFY_CARD, ], }); diff --git a/tests/unit/APITest.ts b/tests/unit/APITest.ts index 9362d9bd2f45..4f25a136a795 100644 --- a/tests/unit/APITest.ts +++ b/tests/unit/APITest.ts @@ -874,8 +874,8 @@ describe('API.write() persistence guarantees', () => { let requestPersistedBeforeOptimistic = false; const updateMock = jest.spyOn(Onyx, 'update').mockImplementation((data) => { - // We use ONYXKEYS.IS_CHECKING_PUBLIC_ROOM as a sample key to identify the marker - const hasMarker = data.some((entry) => entry.key === ONYXKEYS.IS_CHECKING_PUBLIC_ROOM); + // We use ONYXKEYS.RAM_ONLY_IS_CHECKING_PUBLIC_ROOM as a sample key to identify the marker + const hasMarker = data.some((entry) => entry.key === ONYXKEYS.RAM_ONLY_IS_CHECKING_PUBLIC_ROOM); if (hasMarker) { optimisticDataApplied = true; requestPersistedBeforeOptimistic = PersistedRequests.getAll().some((r) => r.command === 'MockCommand'); @@ -888,7 +888,7 @@ describe('API.write() persistence guarantees', () => { optimisticData: [ { onyxMethod: Onyx.METHOD.SET, - key: ONYXKEYS.IS_CHECKING_PUBLIC_ROOM, + key: ONYXKEYS.RAM_ONLY_IS_CHECKING_PUBLIC_ROOM, value: true, }, ], diff --git a/tests/unit/ExportOnyxStateTest.ts b/tests/unit/ExportOnyxStateTest.ts index 9d516916ef86..b1037ab04ad1 100644 --- a/tests/unit/ExportOnyxStateTest.ts +++ b/tests/unit/ExportOnyxStateTest.ts @@ -123,7 +123,7 @@ describe('maskOnyxState', () => { session: mockSession, [ONYXKEYS.NVP_PRIVATE_PUSH_NOTIFICATION_ID]: 'sensitive-id', [ONYXKEYS.NVP_PRIVATE_STRIPE_CUSTOMER_ID]: 'stripe-id', - [ONYXKEYS.PLAID_LINK_TOKEN]: 'plaid-token', + [ONYXKEYS.RAM_ONLY_PLAID_LINK_TOKEN]: 'plaid-token', [ONYXKEYS.ONFIDO_TOKEN]: 'onfido-token', }; const result = maskOnyxState(input); @@ -131,7 +131,7 @@ describe('maskOnyxState', () => { // Sensitive keys should be removed expect(result[ONYXKEYS.NVP_PRIVATE_PUSH_NOTIFICATION_ID]).toBeUndefined(); expect(result[ONYXKEYS.NVP_PRIVATE_STRIPE_CUSTOMER_ID]).toBeUndefined(); - expect(result[ONYXKEYS.PLAID_LINK_TOKEN]).toBeUndefined(); + expect(result[ONYXKEYS.RAM_ONLY_PLAID_LINK_TOKEN]).toBeUndefined(); expect(result[ONYXKEYS.ONFIDO_TOKEN]).toBeUndefined(); // Session should still be present