diff --git a/src/pages/inbox/sidebar/BaseSidebarScreen.tsx b/src/pages/inbox/sidebar/BaseSidebarScreen.tsx
index ddb3207cdf2f1..eec9c3e01fdde 100644
--- a/src/pages/inbox/sidebar/BaseSidebarScreen.tsx
+++ b/src/pages/inbox/sidebar/BaseSidebarScreen.tsx
@@ -1,21 +1,49 @@
import React from 'react';
import {View} from 'react-native';
+import Onyx from 'react-native-onyx';
import NavigationTabBar from '@components/Navigation/NavigationTabBar';
import NAVIGATION_TABS from '@components/Navigation/NavigationTabBar/NAVIGATION_TABS';
import TopBar from '@components/Navigation/TopBar';
+import OptionsListSkeletonView from '@components/OptionsListSkeletonView';
import ScreenWrapper from '@components/ScreenWrapper';
+import useConfirmReadyToOpenApp from '@hooks/useConfirmReadyToOpenApp';
import useLocalize from '@hooks/useLocalize';
+import useOnyx from '@hooks/useOnyx';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useThemeStyles from '@hooks/useThemeStyles';
import {isMobile} from '@libs/Browser';
+import ONYXKEYS from '@src/ONYXKEYS';
import SidebarLinksData from './SidebarLinksData';
+// Once the app finishes loading for the first time, we never show the skeleton again
+// (even if isLoadingApp briefly flips back to true during a reconnect).
+// This uses a module-level variable + connectWithoutView instead of a ref because
+// a ref resets on unmount, so the skeleton would flash again when the component
+// remounts (e.g. navigating between tabs).
+let hasEverFinishedLoading = false;
+Onyx.connectWithoutView({
+ key: ONYXKEYS.IS_LOADING_APP,
+ callback: (value) => {
+ if (value !== false) {
+ return;
+ }
+ hasEverFinishedLoading = true;
+ },
+});
+
function BaseSidebarScreen() {
const styles = useThemeStyles();
const {translate} = useLocalize();
const {shouldUseNarrowLayout} = useResponsiveLayout();
const shouldDisplayLHB = !shouldUseNarrowLayout;
+ const [isLoadingApp = true] = useOnyx(ONYXKEYS.IS_LOADING_APP);
+ const shouldShowSkeleton = isLoadingApp && !hasEverFinishedLoading;
+
+ // Must be called unconditionally so openApp() can proceed even when
+ // the skeleton is shown and SidebarLinksData has not mounted yet.
+ useConfirmReadyToOpenApp();
+
return (
-
-
-
+ {shouldShowSkeleton ? : }
{shouldDisplayLHB && }
>
)}
diff --git a/src/pages/inbox/sidebar/SidebarLinks.tsx b/src/pages/inbox/sidebar/SidebarLinks.tsx
index ca50f7672fc14..e0a1128474b07 100644
--- a/src/pages/inbox/sidebar/SidebarLinks.tsx
+++ b/src/pages/inbox/sidebar/SidebarLinks.tsx
@@ -6,6 +6,7 @@ import type {ValueOf} from 'type-fest';
import LHNOptionsList from '@components/LHNOptionsList/LHNOptionsList';
import OptionsListSkeletonView from '@components/OptionsListSkeletonView';
import useConfirmReadyToOpenApp from '@hooks/useConfirmReadyToOpenApp';
+import useOnyx from '@hooks/useOnyx';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useStyleUtils from '@hooks/useStyleUtils';
import useThemeStyles from '@hooks/useThemeStyles';
@@ -14,6 +15,7 @@ import Navigation from '@libs/Navigation/Navigation';
import {cancelSpan} from '@libs/telemetry/activeSpans';
import * as ReportActionContextMenu from '@pages/inbox/report/ContextMenu/ReportActionContextMenu';
import CONST from '@src/CONST';
+import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {Report} from '@src/types/onyx';
@@ -24,9 +26,6 @@ type SidebarLinksProps = {
/** List of options to display */
optionListItems: Report[];
- /** Whether the reports are loading. When false it means they are ready to be used. */
- isLoading: OnyxEntry;
-
/** The chat priority mode */
priorityMode?: OnyxEntry>;
@@ -34,10 +33,11 @@ type SidebarLinksProps = {
isActiveReport: (reportID: string) => boolean;
};
-function SidebarLinks({insets, optionListItems, isLoading, priorityMode = CONST.PRIORITY_MODE.DEFAULT, isActiveReport}: SidebarLinksProps) {
+function SidebarLinks({insets, optionListItems, priorityMode = CONST.PRIORITY_MODE.DEFAULT, isActiveReport}: SidebarLinksProps) {
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const {shouldUseNarrowLayout} = useResponsiveLayout();
+ const [isLoadingReportData = true] = useOnyx(ONYXKEYS.IS_LOADING_REPORT_DATA);
useConfirmReadyToOpenApp();
@@ -90,7 +90,7 @@ function SidebarLinks({insets, optionListItems, isLoading, priorityMode = CONST.
optionMode={viewMode}
onFirstItemRendered={setSidebarLoaded}
/>
- {!!isLoading && optionListItems?.length === 0 && (
+ {!!isLoadingReportData && optionListItems?.length === 0 && (
diff --git a/src/pages/inbox/sidebar/SidebarLinksData.tsx b/src/pages/inbox/sidebar/SidebarLinksData.tsx
index b5b489af85a3b..c47562b8b2772 100644
--- a/src/pages/inbox/sidebar/SidebarLinksData.tsx
+++ b/src/pages/inbox/sidebar/SidebarLinksData.tsx
@@ -21,7 +21,6 @@ function SidebarLinksData({insets}: SidebarLinksDataProps) {
const isFocused = useIsFocused();
const styles = useThemeStyles();
const {translate} = useLocalize();
- const [isLoadingApp = true] = useOnyx(ONYXKEYS.IS_LOADING_APP);
const [priorityMode = CONST.PRIORITY_MODE.DEFAULT] = useOnyx(ONYXKEYS.NVP_PRIORITY_MODE);
const {orderedReports, currentReportID} = useSidebarOrderedReportsState('SidebarLinksData');
@@ -62,7 +61,6 @@ function SidebarLinksData({insets}: SidebarLinksDataProps) {
priorityMode={priorityMode ?? CONST.PRIORITY_MODE.DEFAULT}
// Data props:
isActiveReport={isActiveReport}
- isLoading={isLoadingApp ?? false}
optionListItems={orderedReports}
/>
diff --git a/tests/ui/GroupChatNameTests.tsx b/tests/ui/GroupChatNameTests.tsx
index c29b6f3434d04..110f33cb4cfaf 100644
--- a/tests/ui/GroupChatNameTests.tsx
+++ b/tests/ui/GroupChatNameTests.tsx
@@ -197,6 +197,7 @@ function signInAndGetApp(reportName = '', participantAccountIDs?: number[]): Pro
// Simulate setting an unread report and personal details
await act(async () => {
await Promise.all([
+ Onyx.merge(ONYXKEYS.IS_LOADING_APP, false),
Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, {
reportID: REPORT_ID,
reportName,
diff --git a/tests/ui/PaginationTest.tsx b/tests/ui/PaginationTest.tsx
index d2bebf6fca7ab..48326835cd7ab 100644
--- a/tests/ui/PaginationTest.tsx
+++ b/tests/ui/PaginationTest.tsx
@@ -232,6 +232,7 @@ async function signInAndGetApp(): Promise {
await act(async () => {
await Promise.all([
+ Onyx.merge(ONYXKEYS.IS_LOADING_APP, false),
// Simulate setting an unread report and personal details
Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, {
reportID: REPORT_ID,
diff --git a/tests/ui/SessionTest.tsx b/tests/ui/SessionTest.tsx
index 00221d6945fa5..6f3b64b28b4a5 100644
--- a/tests/ui/SessionTest.tsx
+++ b/tests/ui/SessionTest.tsx
@@ -95,6 +95,7 @@ describe('Deep linking', () => {
jest.spyOn(AppActions, 'openApp').mockImplementation(async () => {
const originalResult = await originalOpenApp();
await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, report);
+ await Onyx.merge(ONYXKEYS.IS_LOADING_APP, false);
return originalResult;
});
});
diff --git a/tests/ui/UnreadIndicatorsTest.tsx b/tests/ui/UnreadIndicatorsTest.tsx
index c4fb2ce87997c..9f748bac66048 100644
--- a/tests/ui/UnreadIndicatorsTest.tsx
+++ b/tests/ui/UnreadIndicatorsTest.tsx
@@ -215,6 +215,7 @@ async function signInAndGetAppWithUnreadChat(): Promise {
};
await Promise.all([
+ Onyx.merge(ONYXKEYS.IS_LOADING_APP, false),
Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, personalDetails),
Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, report),
Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${REPORT_ID}`, reportActions),