1- import React , { Suspense , useEffect , useState } from 'react' ;
1+ import React , { startTransition , useEffect , useState } from 'react' ;
22import DelegateNoAccessModalProvider from './components/DelegateNoAccessModalProvider' ;
33import EmojiPicker from './components/EmojiPicker/EmojiPicker' ;
44import GrowlNotification from './components/GrowlNotification' ;
5- import ProactiveAppReviewModalManager from './components/ProactiveAppReviewModalManager' ;
6- import ScreenShareRequestModal from './components/ScreenShareRequestModal' ;
7- import UpdateAppModal from './components/UpdateAppModal' ;
5+ import LazyModalSlot from './components/LazyModalSlot' ;
86import * as EmojiPickerAction from './libs/actions/EmojiPickerAction' ;
97import { growlRef } from './libs/Growl' ;
108import * as ReportActionContextMenu from './pages/inbox/report/ContextMenu/ReportActionContextMenu' ;
119
1210const LazyPopoverReportActionContextMenu = React . lazy ( ( ) => import ( './pages/inbox/report/ContextMenu/PopoverReportActionContextMenu' ) ) ;
11+ const LazyUpdateAppModal = React . lazy ( ( ) => import ( './components/UpdateAppModal' ) ) ;
12+ const LazyScreenShareRequestModal = React . lazy ( ( ) => import ( './components/ScreenShareRequestModal' ) ) ;
13+ const LazyProactiveAppReviewModalManager = React . lazy ( ( ) => import ( './components/ProactiveAppReviewModalManager' ) ) ;
1314
1415// Maximum time (ms) the context menu mount can stay deferred before requestIdleCallback forces it to run,
1516// guaranteeing mount even if the main thread never becomes idle.
@@ -20,40 +21,60 @@ const IDLE_CALLBACK_TIMEOUT_MS = 2000;
2021 */
2122function GlobalModals ( ) {
2223 const [ shouldRenderContextMenu , setShouldRenderContextMenu ] = useState ( false ) ;
24+ const [ shouldRenderDeferredModals , setShouldRenderDeferredModals ] = useState ( false ) ;
2325
26+ // Defer loading the context menu and rare-condition modals until after startup to avoid
27+ // pulling in their dependencies (ContextMenuActions, ReportUtils, ModifiedExpenseMessage,
28+ // ProactiveAppReviewModal, etc.) and their useOnyx subscriptions during the ManualAppStartup span.
2429 useEffect ( ( ) => {
25- // Defer loading the context menu until after startup to avoid pulling in heavy
26- // dependencies (ContextMenuActions, ReportUtils, ModifiedExpenseMessage, etc.)
27- // during the ManualAppStartup span.
28- const id = requestIdleCallback ( ( ) => setShouldRenderContextMenu ( true ) , { timeout : IDLE_CALLBACK_TIMEOUT_MS } ) ;
30+ const id = requestIdleCallback (
31+ ( ) => {
32+ startTransition ( ( ) => {
33+ setShouldRenderContextMenu ( true ) ;
34+ setShouldRenderDeferredModals ( true ) ;
35+ } ) ;
36+ } ,
37+ { timeout : IDLE_CALLBACK_TIMEOUT_MS } ,
38+ ) ;
39+ return ( ) => cancelIdleCallback ( id ) ;
40+ } , [ ] ) ;
2941
30- // Allow showContextMenu() to force eager mount if the user interacts before the idle callback fires.
42+ // Allow showContextMenu() to force eager mount if the user interacts before the idle callback fires.
43+ useEffect ( ( ) => {
3144 ReportActionContextMenu . registerEnsureContextMenuMounted ( ( ) => setShouldRenderContextMenu ( true ) ) ;
32-
33- return ( ) => {
34- cancelIdleCallback ( id ) ;
35- ReportActionContextMenu . registerEnsureContextMenuMounted ( null ) ;
36- } ;
45+ return ( ) => ReportActionContextMenu . registerEnsureContextMenuMounted ( null ) ;
3746 } , [ ] ) ;
3847
3948 return (
4049 < >
41- < UpdateAppModal />
42- { /* Those below are only available to the authenticated user. */ }
4350 < GrowlNotification ref = { growlRef } />
4451 < DelegateNoAccessModalProvider >
4552 { shouldRenderContextMenu && (
46- < Suspense fallback = { null } >
53+ < LazyModalSlot >
4754 { /* eslint-disable-next-line react-hooks/refs -- module-level createRef, safe to pass as ref prop */ }
4855 < LazyPopoverReportActionContextMenu ref = { ReportActionContextMenu . contextMenuRef } />
49- </ Suspense >
56+ </ LazyModalSlot >
5057 ) }
5158 </ DelegateNoAccessModalProvider >
5259 { /* eslint-disable-next-line react-hooks/refs -- module-level createRef, safe to pass as ref prop */ }
5360 < EmojiPicker ref = { EmojiPickerAction . emojiPickerRef } />
54- { /* Proactive app review modal shown when user has completed a trigger action */ }
55- < ProactiveAppReviewModalManager />
56- < ScreenShareRequestModal />
61+ { shouldRenderDeferredModals && (
62+ < >
63+ { /* Order matters: BaseModal hardcodes zIndex: 1 on every modal, so DOM source order
64+ determines stacking when modals coincide. UpdateAppModal is last so the forced-update
65+ prompt sits on top if it ever overlaps with the others. */ }
66+ < LazyModalSlot >
67+ { /* Proactive app review modal shown when user has completed a trigger action */ }
68+ < LazyProactiveAppReviewModalManager />
69+ </ LazyModalSlot >
70+ < LazyModalSlot >
71+ < LazyScreenShareRequestModal />
72+ </ LazyModalSlot >
73+ < LazyModalSlot >
74+ < LazyUpdateAppModal />
75+ </ LazyModalSlot >
76+ </ >
77+ ) }
5778 </ >
5879 ) ;
5980}
0 commit comments