diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportView.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportView.tsx index 8013b3e8884b..14b16356a090 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportView.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportView.tsx @@ -6,6 +6,7 @@ import MoneyRequestReceiptView from '@components/ReportActionItem/MoneyRequestRe import ReportActionsSkeletonView from '@components/ReportActionsSkeletonView'; import ReportHeaderSkeletonView from '@components/ReportHeaderSkeletonView'; +import {useIsAppLoadPending} from '@hooks/useInFlightRequests'; import useMarkOpenReportEndOnSkeleton from '@hooks/useMarkOpenReportEndOnSkeleton'; import useNetwork from '@hooks/useNetwork'; import useOnyx from '@hooks/useOnyx'; @@ -122,7 +123,7 @@ function MoneyRequestReportView({report, reportLoadingState, shouldDisplayReport const {isSmallScreenWidth} = useResponsiveLayout(); const reportID = report?.reportID; - const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP); + const isAppLoadPending = useIsAppLoadPending(); const {reportPendingAction, reportErrors: allReportErrors} = getReportOfflinePendingActionAndErrors(report); const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(report?.chatReportID)}`); @@ -233,7 +234,7 @@ function MoneyRequestReportView({report, reportLoadingState, shouldDisplayReport return; } - if (isLoadingApp) { + if (isAppLoadPending) { return ( diff --git a/src/components/Search/FilterComponents/WorkspaceSelector.tsx b/src/components/Search/FilterComponents/WorkspaceSelector.tsx index c3d82e2a611b..8dc4b58bdd56 100644 --- a/src/components/Search/FilterComponents/WorkspaceSelector.tsx +++ b/src/components/Search/FilterComponents/WorkspaceSelector.tsx @@ -6,6 +6,7 @@ import type {ListItem, TextInputOptions} from '@components/SelectionList/types'; import {advancedSearchPoliciesSelector, useAdvancedSearchFiltersWorkspaces} from '@hooks/useAdvancedSearchFilters'; import useDebouncedState from '@hooks/useDebouncedState'; +import {useIsAppLoadPending} from '@hooks/useInFlightRequests'; import useInitialValue from '@hooks/useInitialValue'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; @@ -40,7 +41,7 @@ function WorkspaceSelector({value = [], selectionListTextInputStyle, selectionLi const {translate} = useLocalize(); const theme = useTheme(); const styles = useThemeStyles(); - const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP); + const isAppLoadPending = useIsAppLoadPending(); const [policies = getEmptyObject>>(), policiesResult] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {selector: advancedSearchPoliciesSelector}); const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState(''); // Fetch the full (unfiltered) workspace list and apply the search filter locally, so pinning is decided from the @@ -98,7 +99,7 @@ function WorkspaceSelector({value = [], selectionListTextInputStyle, selectionLi itemCount={listData.length} isSearchable={shouldShowWorkspaceSearchInput} > - {isLoadingApp && !isOffline ? ( + {isAppLoadPending && !isOffline ? ( (null); const [allPolicyTags] = useOnyx(ONYXKEYS.COLLECTION.POLICY_TAGS); @@ -276,7 +277,7 @@ function DynamicNewReportWorkspaceSelectionPage({route}: NewReportWorkspaceSelec ) : ( diff --git a/src/pages/DynamicReportChangeWorkspacePage.tsx b/src/pages/DynamicReportChangeWorkspacePage.tsx index 54b39d3d58ee..b739a197b7a5 100644 --- a/src/pages/DynamicReportChangeWorkspacePage.tsx +++ b/src/pages/DynamicReportChangeWorkspacePage.tsx @@ -9,6 +9,7 @@ import UserListItem from '@components/SelectionList/ListItem/UserListItem'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useDebouncedState from '@hooks/useDebouncedState'; import useDynamicBackPath from '@hooks/useDynamicBackPath'; +import {useIsAppLoadPending} from '@hooks/useInFlightRequests'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useOnyx from '@hooks/useOnyx'; @@ -73,13 +74,13 @@ function DynamicReportChangeWorkspacePage({report}: DynamicReportChangeWorkspace const [policies, fetchStatus] = useOnyx(ONYXKEYS.COLLECTION.POLICY); const [reportNextStep] = useOnyx(`${ONYXKEYS.COLLECTION.NEXT_STEP}${reportID}`); const [isChangePolicyTrainingModalDismissed = false] = useOnyx(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING, {selector: changePolicyTrainingModalDismissedSelector}); - const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP); + const isAppLoadPending = useIsAppLoadPending(); const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); const isReportLastVisibleArchived = useReportIsArchived(report?.parentReportID); const [submitterLogin] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {selector: personalDetailsLoginSelector(report?.ownerAccountID)}, [report?.ownerAccountID]); const [doesSubmitterPersonalDetailExist] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {selector: doesPersonalDetailExistSelector(report?.ownerAccountID)}, [report?.ownerAccountID]); const [managerLogin] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {selector: personalDetailsLoginSelector(report?.managerID)}, [report?.managerID]); - const shouldShowLoadingIndicator = isLoadingApp && !isOffline; + const shouldShowLoadingIndicator = isAppLoadPending && !isOffline; const {isBetaEnabled} = usePermissions(); const isASAPSubmitBetaEnabled = isBetaEnabled(CONST.BETAS.ASAP_SUBMIT); const session = useSession(); @@ -213,7 +214,7 @@ function DynamicReportChangeWorkspacePage({report}: DynamicReportChangeWorkspace ) : ( diff --git a/src/pages/SetDefaultWorkspacePage.tsx b/src/pages/SetDefaultWorkspacePage.tsx index 98f1003b06ce..37f006bcf192 100644 --- a/src/pages/SetDefaultWorkspacePage.tsx +++ b/src/pages/SetDefaultWorkspacePage.tsx @@ -7,6 +7,7 @@ import type {WorkspaceListItemType} from '@components/SelectionList/ListItem/typ import UserListItem from '@components/SelectionList/ListItem/UserListItem'; import useDebouncedState from '@hooks/useDebouncedState'; +import {useIsAppLoadPending} from '@hooks/useInFlightRequests'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useOnyx from '@hooks/useOnyx'; @@ -39,10 +40,10 @@ function SetDefaultWorkspacePage({route}: SetDefaultWorkspacePageProps) { const {translate, localeCompare} = useLocalize(); const [policies, fetchStatus] = useOnyx(ONYXKEYS.COLLECTION.POLICY); - const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP); + const isAppLoadPending = useIsAppLoadPending(); const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID); - const shouldShowLoadingIndicator = isLoadingApp && !isOffline; + const shouldShowLoadingIndicator = isAppLoadPending && !isOffline; const session = useSession(); const selectPolicy = (selectedPolicyID?: string) => { @@ -103,7 +104,7 @@ function SetDefaultWorkspacePage({route}: SetDefaultWorkspacePageProps) { ) : ( diff --git a/src/pages/domain/DomainsListPage.tsx b/src/pages/domain/DomainsListPage.tsx index 998029e337b5..4d7eab5ccbea 100644 --- a/src/pages/domain/DomainsListPage.tsx +++ b/src/pages/domain/DomainsListPage.tsx @@ -6,6 +6,7 @@ import WorkspaceListLayout from '@components/WorkspaceListLayout'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useDocumentTitle from '@hooks/useDocumentTitle'; +import {useIsAppLoadPending} from '@hooks/useInFlightRequests'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; @@ -36,7 +37,7 @@ function DomainsListPage() { useDocumentTitle(translate('common.domains')); - const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP); + const isAppLoadPending = useIsAppLoadPending(); const [allDomains] = useOnyx(ONYXKEYS.COLLECTION.DOMAIN); const [allDomainErrors] = useOnyx(ONYXKEYS.COLLECTION.DOMAIN_ERRORS); @@ -49,7 +50,7 @@ function DomainsListPage() { }; const domainRows: DomainRowData[] = []; - const shouldShowLoadingIndicator = !!isLoadingApp && !isOffline; + const shouldShowLoadingIndicator = isAppLoadPending && !isOffline; if (!isEmptyObject(allDomains)) { for (const domain of Object.values(allDomains)) { diff --git a/src/pages/inbox/ReportActions.tsx b/src/pages/inbox/ReportActions.tsx index 24308793174c..c782bbe15dbe 100644 --- a/src/pages/inbox/ReportActions.tsx +++ b/src/pages/inbox/ReportActions.tsx @@ -1,5 +1,6 @@ import MoneyRequestReportActionsList from '@components/MoneyRequestReportView/MoneyRequestReportActionsList'; +import {useIsAppLoadPending} from '@hooks/useInFlightRequests'; import useMarkOpenReportEndOnSkeleton from '@hooks/useMarkOpenReportEndOnSkeleton'; import useNetwork from '@hooks/useNetwork'; import useOnyx from '@hooks/useOnyx'; @@ -46,7 +47,7 @@ function ReportActions() { const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportIDFromRoute}`); const [conciergeReportID] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID); const [reportLoadingState = defaultReportLoadingState] = useOnyx(`${ONYXKEYS.COLLECTION.RAM_ONLY_REPORT_LOADING_STATE}${reportIDFromRoute}`); - const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP); + const isAppLoadPending = useIsAppLoadPending(); const {reportActions} = usePaginatedReportActions(reportIDFromRoute); const allReportTransactions = useReportTransactionsCollection(reportIDFromRoute); @@ -65,7 +66,7 @@ function ReportActions() { // Concierge is excluded so the body still mounts under the app-load skeleton, seeding sessionStartTime // before content appeared. const isConciergeMainDM = isConciergeChatReport(report, conciergeReportID); - const shouldShowAppLoadSkeleton = !!isLoadingApp && !isOffline && !!report && !shouldWaitForTransactions && !shouldDisplayMoneyRequestActionsList && !isConciergeMainDM; + const shouldShowAppLoadSkeleton = isAppLoadPending && !isOffline && !!report && !shouldWaitForTransactions && !shouldDisplayMoneyRequestActionsList && !isConciergeMainDM; useMarkOpenReportEndOnSkeleton(report, shouldShowAppLoadSkeleton); diff --git a/src/pages/settings/Profile/ProfilePage.tsx b/src/pages/settings/Profile/ProfilePage.tsx index 36bee745ac45..fb4e2809c975 100755 --- a/src/pages/settings/Profile/ProfilePage.tsx +++ b/src/pages/settings/Profile/ProfilePage.tsx @@ -15,6 +15,7 @@ import Section from '@components/Section'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useDocumentTitle from '@hooks/useDocumentTitle'; +import {useIsAppLoadPending} from '@hooks/useInFlightRequests'; import {useMemoizedLazyAsset, useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; @@ -69,7 +70,7 @@ function ProfilePage() { const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const route = useRoute>(); useDocumentTitle(translate('common.profile')); - const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP); + const isAppLoadPending = useIsAppLoadPending(); const getPronouns = (): string => { const pronounsKey = currentUserPersonalDetails?.pronouns?.replace(CONST.PRONOUNS.PREFIX, '') ?? ''; return pronounsKey ? translate(`pronouns.${pronounsKey}` as TranslationPaths) : translate('profilePage.selectYourPronouns'); @@ -182,7 +183,7 @@ function ProfilePage() { const privateSectionReasonAttributes: SkeletonSpanReasonAttributes = { context: 'ProfilePage.privateSection', - isLoadingApp: !!isLoadingApp, + isLoadingApp: isAppLoadPending, }; return ( @@ -292,7 +293,7 @@ function ProfilePage() { childrenStyles={styles.pt3} titleStyles={styles.accountSettingsSectionTitle} > - {isLoadingApp ? ( + {isAppLoadPending ? ( ()] = useOnyx(ONYXKEYS.WALLET_TERMS); - const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP); + const isAppLoadPending = useIsAppLoadPending(); const [userAccount] = useOnyx(ONYXKEYS.ACCOUNT); const [lastUsedPaymentMethods] = useOnyx(ONYXKEYS.NVP_LAST_PAYMENT_METHOD); const [personalPolicyID] = useOnyx(ONYXKEYS.PERSONAL_POLICY_ID); @@ -661,8 +662,8 @@ function WalletPage() { ]; }, [bottomMountItem, confirmDeleteCard, icons.MoneySearch, icons.Table, icons.Trashcan, paymentMethod.methodID, selectedCard?.bank, shouldUseNarrowLayout, translate]); - if (isLoadingApp) { - const reasonAttributes: SkeletonSpanReasonAttributes = {context: 'WalletPage', isLoadingApp: !!isLoadingApp}; + if (isAppLoadPending) { + const reasonAttributes: SkeletonSpanReasonAttributes = {context: 'WalletPage', isLoadingApp: isAppLoadPending}; return ( { mockUseConciergeSessionActions.mockReturnValue({startSession: jest.fn(), setShowFullHistory: jest.fn(), setHadMessagesAtSessionStart: jest.fn()}); mockUseOnyx.mockImplementation((key: string) => { - if (key === ONYXKEYS.IS_LOADING_APP) { + // useReportActionsListModel derives app-load state from the request queue via useIsAppLoadPending, + // which reads these queue keys through selectors that resolve to a boolean. Returning that boolean + // directly mirrors what useOnyx yields once the selector runs. The legacy IS_LOADING_APP flag is kept + // in the fixture for any component still reading it directly. + if (key === ONYXKEYS.IS_LOADING_APP || key === ONYXKEYS.PERSISTED_REQUESTS || key === ONYXKEYS.PERSISTED_ONGOING_REQUESTS) { return [false, {status: 'loaded'}]; } if (key === ONYXKEYS.RAM_ONLY_ARE_TRANSLATIONS_LOADING) { @@ -276,7 +280,7 @@ describe('ReportActionsList (body)', () => { }); mockUseOnyx.mockImplementation((key: string) => { - if (key === ONYXKEYS.IS_LOADING_APP) { + if (key === ONYXKEYS.IS_LOADING_APP || key === ONYXKEYS.PERSISTED_REQUESTS || key === ONYXKEYS.PERSISTED_ONGOING_REQUESTS) { return [true, {status: 'loaded'}]; } if (key === ONYXKEYS.RAM_ONLY_ARE_TRANSLATIONS_LOADING) { @@ -317,7 +321,7 @@ describe('ReportActionsList (body)', () => { }); mockUseOnyx.mockImplementation((key: string) => { - if (key === ONYXKEYS.IS_LOADING_APP) { + if (key === ONYXKEYS.IS_LOADING_APP || key === ONYXKEYS.PERSISTED_REQUESTS || key === ONYXKEYS.PERSISTED_ONGOING_REQUESTS) { return [false, {status: 'loaded'}]; } if (key === ONYXKEYS.RAM_ONLY_ARE_TRANSLATIONS_LOADING) { @@ -352,7 +356,7 @@ describe('ReportActionsList (body)', () => { }); mockUseOnyx.mockImplementation((key: string) => { - if (key === ONYXKEYS.IS_LOADING_APP) { + if (key === ONYXKEYS.IS_LOADING_APP || key === ONYXKEYS.PERSISTED_REQUESTS || key === ONYXKEYS.PERSISTED_ONGOING_REQUESTS) { return [true, {status: 'loaded'}]; } if (key === ONYXKEYS.RAM_ONLY_ARE_TRANSLATIONS_LOADING) { @@ -384,7 +388,7 @@ describe('ReportActionsList (body)', () => { }); mockUseOnyx.mockImplementation((key: string) => { - if (key === ONYXKEYS.IS_LOADING_APP) { + if (key === ONYXKEYS.IS_LOADING_APP || key === ONYXKEYS.PERSISTED_REQUESTS || key === ONYXKEYS.PERSISTED_ONGOING_REQUESTS) { return [false, {status: 'loaded'}]; } if (key === ONYXKEYS.RAM_ONLY_ARE_TRANSLATIONS_LOADING) { @@ -416,7 +420,7 @@ describe('ReportActionsList (body)', () => { mockUseNetwork.mockReturnValue({isOffline: false}); mockUseOnyx.mockImplementation((key: string) => { - if (key === ONYXKEYS.IS_LOADING_APP) { + if (key === ONYXKEYS.IS_LOADING_APP || key === ONYXKEYS.PERSISTED_REQUESTS || key === ONYXKEYS.PERSISTED_ONGOING_REQUESTS) { return [true, {status: 'loaded'}]; } if (key === ONYXKEYS.RAM_ONLY_ARE_TRANSLATIONS_LOADING) { @@ -501,7 +505,7 @@ describe('ReportActionsList (body)', () => { if (key === ONYXKEYS.CONCIERGE_REPORT_ID) { return [CONCIERGE_REPORT_ID, {status: 'loaded'}]; } - if (key === ONYXKEYS.IS_LOADING_APP) { + if (key === ONYXKEYS.IS_LOADING_APP || key === ONYXKEYS.PERSISTED_REQUESTS || key === ONYXKEYS.PERSISTED_ONGOING_REQUESTS) { return [false, {status: 'loaded'}]; } if (key === ONYXKEYS.RAM_ONLY_ARE_TRANSLATIONS_LOADING) { @@ -662,7 +666,7 @@ describe('ReportActionsList (body)', () => { if (key === ONYXKEYS.CONCIERGE_REPORT_ID) { return [CONCIERGE_REPORT_ID, {status: 'loaded'}]; } - if (key === ONYXKEYS.IS_LOADING_APP) { + if (key === ONYXKEYS.IS_LOADING_APP || key === ONYXKEYS.PERSISTED_REQUESTS || key === ONYXKEYS.PERSISTED_ONGOING_REQUESTS) { return [false, {status: 'loaded'}]; } if (key === ONYXKEYS.RAM_ONLY_ARE_TRANSLATIONS_LOADING) { diff --git a/tests/ui/ReportActionsTest.tsx b/tests/ui/ReportActionsTest.tsx index af6f66bbe2f0..a3410eaa614e 100644 --- a/tests/ui/ReportActionsTest.tsx +++ b/tests/ui/ReportActionsTest.tsx @@ -97,7 +97,7 @@ const mockReport: OnyxTypes.Report = { type ReportLoadingStateOverrides = Partial<{isLoadingInitialReportActions: boolean; hasOnceLoadedReportActions: boolean}>; -/** Builds the keyed useOnyx mock the orchestrator reads: the report, its loading state and IS_LOADING_APP. */ +/** Builds the keyed useOnyx mock the orchestrator reads: the report, its loading state and the app-load request queue. */ const setupUseOnyx = (options: {report?: OnyxTypes.Report | undefined; isLoadingApp?: boolean; loadingState?: ReportLoadingStateOverrides} = {}) => { // `'report' in options` distinguishes "not passed" (default to mockReport) from an explicit // `{report: undefined}` (the report-not-available case) — a destructuring default would swallow the latter. @@ -105,7 +105,10 @@ const setupUseOnyx = (options: {report?: OnyxTypes.Report | undefined; isLoading const isLoadingApp = options.isLoadingApp ?? false; const loadingState = options.loadingState ?? {}; mockUseOnyx.mockImplementation((key: string) => { - if (key === ONYXKEYS.IS_LOADING_APP) { + // ReportActions derives app-load state from the request queue via useIsAppLoadPending, which reads these + // queue keys through selectors that resolve to a boolean. Returning that boolean directly mirrors what + // useOnyx yields once the selector runs. The legacy IS_LOADING_APP flag is kept for any direct reader. + if (key === ONYXKEYS.IS_LOADING_APP || key === ONYXKEYS.PERSISTED_REQUESTS || key === ONYXKEYS.PERSISTED_ONGOING_REQUESTS) { return [isLoadingApp, {status: 'loaded'}]; } if (key.includes('reportLoadingState')) {