From ebce33dbb95c54efe2906c22b34b5bfa5f3e7ddf Mon Sep 17 00:00:00 2001 From: war-in Date: Tue, 18 Nov 2025 11:35:29 +0100 Subject: [PATCH 1/7] feat: emit modalClosed from RHP once closed --- src/CONST/index.ts | 4 ++++ .../AppNavigator/Navigators/RightModalNavigator.tsx | 6 ++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/CONST/index.ts b/src/CONST/index.ts index eafa0bfe2ec5..ad9dafa7b83e 100755 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -7397,6 +7397,10 @@ const CONST = { INVITE_ACCOUNTANT: 'inviteAccountant', ADD_EXPENSE_APPROVALS: 'addExpenseApprovals', }, + + MODAL_EVENTS: { + CLOSED: 'modalClosed' + }, } as const; const CONTINUATION_DETECTION_SEARCH_FILTER_KEYS = [ diff --git a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx index 1e2b5003d38a..67f73f107525 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx @@ -1,9 +1,9 @@ import type {NavigatorScreenParams} from '@react-navigation/native'; -import React, {useCallback, useContext, useMemo, useRef} from 'react'; +import React, {useCallback, useContext, useMemo, useRef, useEffect} from 'react'; // We use Animated for all functionality related to wide RHP to make it easier // to interact with react-navigation components (e.g., CardContainer, interpolator), which also use Animated. // eslint-disable-next-line no-restricted-imports -import {Animated, InteractionManager} from 'react-native'; +import {Animated, InteractionManager, DeviceEventEmitter} from 'react-native'; import NoDropZone from '@components/DragAndDrop/NoDropZone'; import {expandedRHPProgress, WideRHPContext} from '@components/WideRHPContextProvider'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; @@ -65,6 +65,8 @@ function RightModalNavigator({navigation, route}: RightModalNavigatorProps) { }, CONST.ANIMATED_TRANSITION); }, [navigation]); + useEffect(() => () => DeviceEventEmitter.emit(CONST.MODAL_EVENTS.CLOSED), []); + return ( From 42f10f49d6e2d37a24357b684e87aabb55f5dc5c Mon Sep 17 00:00:00 2001 From: war-in Date: Tue, 18 Nov 2025 11:36:00 +0100 Subject: [PATCH 2/7] feat: fire callback after modal dismissed --- src/libs/Navigation/Navigation.ts | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/libs/Navigation/Navigation.ts b/src/libs/Navigation/Navigation.ts index 96be23377cb1..489c24287bc3 100644 --- a/src/libs/Navigation/Navigation.ts +++ b/src/libs/Navigation/Navigation.ts @@ -3,7 +3,7 @@ import type {EventArg, NavigationAction, NavigationContainerEventMap, Navigation import {CommonActions, getPathFromState, StackActions} from '@react-navigation/native'; // eslint-disable-next-line you-dont-need-lodash-underscore/omit import omit from 'lodash/omit'; -import {InteractionManager} from 'react-native'; +import {InteractionManager, DeviceEventEmitter} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import type {Writable} from 'type-fest'; @@ -33,7 +33,15 @@ import setNavigationActionToMicrotaskQueue from './helpers/setNavigationActionTo import {linkingConfig} from './linkingConfig'; import {SPLIT_TO_SIDEBAR} from './linkingConfig/RELATIONS'; import navigationRef from './navigationRef'; -import type {NavigationPartialRoute, NavigationRoute, NavigationStateRoute, ReportsSplitNavigatorParamList, RootNavigatorParamList, State} from './types'; +import type { + NavigationPartialRoute, + NavigationRef, + NavigationRoute, + NavigationStateRoute, + ReportsSplitNavigatorParamList, + RootNavigatorParamList, + State, +} from './types'; // Routes which are part of the flow to set up 2FA const SET_UP_2FA_ROUTES = new Set([ @@ -563,7 +571,7 @@ function getReportRouteByID(reportID?: string, routes: NavigationRoute[] = navig * For detailed information about dismissing modals, * see the NAVIGATION.md documentation. */ -const dismissModal = (ref = navigationRef) => { +const dismissModal = ({ref = navigationRef, callback}: {ref?: NavigationRef; callback?: () => void} = {}) => { isNavigationReady().then(() => { ref.dispatch({type: CONST.NAVIGATION.ACTION_TYPE.DISMISS_MODAL}); // Let React Navigation finish modal transition @@ -571,6 +579,11 @@ const dismissModal = (ref = navigationRef) => { InteractionManager.runAfterInteractions(() => { fireModalDismissed(); }); + + const subscription = DeviceEventEmitter.addListener(CONST.MODAL_EVENTS.CLOSED, () => { + subscription.remove(); + callback?.(); + }); }); }; From 04c241ca8a3283e7f194c413259719cdbdd641c4 Mon Sep 17 00:00:00 2001 From: war-in Date: Tue, 18 Nov 2025 11:36:38 +0100 Subject: [PATCH 3/7] refactor: remove InteractionManager from WorkspaceInviteMessagePage and pass callback to dismissModal --- src/pages/workspace/WorkspaceInviteMessagePage.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/pages/workspace/WorkspaceInviteMessagePage.tsx b/src/pages/workspace/WorkspaceInviteMessagePage.tsx index 685a38100fab..d395933112b7 100644 --- a/src/pages/workspace/WorkspaceInviteMessagePage.tsx +++ b/src/pages/workspace/WorkspaceInviteMessagePage.tsx @@ -1,5 +1,5 @@ import React, {useCallback, useEffect, useMemo, useState} from 'react'; -import {InteractionManager, Keyboard, View} from 'react-native'; +import {Keyboard, View} from 'react-native'; import type {GestureResponderEvent} from 'react-native/Libraries/Types/CoreEventTypes'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; @@ -139,11 +139,7 @@ function WorkspaceInviteMessagePage({policy, route, currentUserPersonalDetails}: } Navigation.setNavigationActionToMicrotaskQueue(() => { - Navigation.dismissModal(); - // eslint-disable-next-line @typescript-eslint/no-deprecated - InteractionManager.runAfterInteractions(() => { - Navigation.navigate(ROUTES.WORKSPACE_MEMBERS.getRoute(route.params.policyID)); - }); + Navigation.dismissModal({callback: () => Navigation.navigate(ROUTES.WORKSPACE_MEMBERS.getRoute(route.params.policyID))}); }); }; From 2564ac5e9c87bb19c21bf4ce4edb52cb52c8baf7 Mon Sep 17 00:00:00 2001 From: war-in Date: Tue, 18 Nov 2025 11:43:56 +0100 Subject: [PATCH 4/7] chore: fix prettier --- src/CONST/index.ts | 2 +- .../AppNavigator/Navigators/RightModalNavigator.tsx | 4 ++-- src/libs/Navigation/Navigation.ts | 12 ++---------- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/src/CONST/index.ts b/src/CONST/index.ts index ad9dafa7b83e..ac2cecf0bf19 100755 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -7399,7 +7399,7 @@ const CONST = { }, MODAL_EVENTS: { - CLOSED: 'modalClosed' + CLOSED: 'modalClosed', }, } as const; diff --git a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx index 67f73f107525..8fa8204d6a3d 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx @@ -1,9 +1,9 @@ import type {NavigatorScreenParams} from '@react-navigation/native'; -import React, {useCallback, useContext, useMemo, useRef, useEffect} from 'react'; +import React, {useCallback, useContext, useEffect, useMemo, useRef} from 'react'; // We use Animated for all functionality related to wide RHP to make it easier // to interact with react-navigation components (e.g., CardContainer, interpolator), which also use Animated. // eslint-disable-next-line no-restricted-imports -import {Animated, InteractionManager, DeviceEventEmitter} from 'react-native'; +import {Animated, DeviceEventEmitter, InteractionManager} from 'react-native'; import NoDropZone from '@components/DragAndDrop/NoDropZone'; import {expandedRHPProgress, WideRHPContext} from '@components/WideRHPContextProvider'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; diff --git a/src/libs/Navigation/Navigation.ts b/src/libs/Navigation/Navigation.ts index 489c24287bc3..316238b013cc 100644 --- a/src/libs/Navigation/Navigation.ts +++ b/src/libs/Navigation/Navigation.ts @@ -3,7 +3,7 @@ import type {EventArg, NavigationAction, NavigationContainerEventMap, Navigation import {CommonActions, getPathFromState, StackActions} from '@react-navigation/native'; // eslint-disable-next-line you-dont-need-lodash-underscore/omit import omit from 'lodash/omit'; -import {InteractionManager, DeviceEventEmitter} from 'react-native'; +import {DeviceEventEmitter, InteractionManager} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import type {Writable} from 'type-fest'; @@ -33,15 +33,7 @@ import setNavigationActionToMicrotaskQueue from './helpers/setNavigationActionTo import {linkingConfig} from './linkingConfig'; import {SPLIT_TO_SIDEBAR} from './linkingConfig/RELATIONS'; import navigationRef from './navigationRef'; -import type { - NavigationPartialRoute, - NavigationRef, - NavigationRoute, - NavigationStateRoute, - ReportsSplitNavigatorParamList, - RootNavigatorParamList, - State, -} from './types'; +import type {NavigationPartialRoute, NavigationRef, NavigationRoute, NavigationStateRoute, ReportsSplitNavigatorParamList, RootNavigatorParamList, State} from './types'; // Routes which are part of the flow to set up 2FA const SET_UP_2FA_ROUTES = new Set([ From 1a347c5a03d8695ebb5320f8d1039a8a193f2acd Mon Sep 17 00:00:00 2001 From: war-in Date: Thu, 20 Nov 2025 18:20:15 +0100 Subject: [PATCH 5/7] refactor: use dismissModal's callback instead of InteractionManager --- .../workspace/members/WorkspaceInviteMessageComponent.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/pages/workspace/members/WorkspaceInviteMessageComponent.tsx b/src/pages/workspace/members/WorkspaceInviteMessageComponent.tsx index 598d105c2866..9f321afddbbf 100644 --- a/src/pages/workspace/members/WorkspaceInviteMessageComponent.tsx +++ b/src/pages/workspace/members/WorkspaceInviteMessageComponent.tsx @@ -1,5 +1,5 @@ import React, {useCallback, useEffect, useMemo, useState} from 'react'; -import {InteractionManager, Keyboard, View} from 'react-native'; +import {Keyboard, View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import type {GestureResponderEvent} from 'react-native/Libraries/Types/CoreEventTypes'; import FormProvider from '@components/Form/FormProvider'; @@ -157,11 +157,7 @@ function WorkspaceInviteMessageComponent({ } Navigation.setNavigationActionToMicrotaskQueue(() => { - Navigation.dismissModal(); - // eslint-disable-next-line @typescript-eslint/no-deprecated - InteractionManager.runAfterInteractions(() => { - Navigation.navigate(ROUTES.WORKSPACE_MEMBERS.getRoute(policyID)); - }); + Navigation.dismissModal({callback: () => Navigation.navigate(ROUTES.WORKSPACE_MEMBERS.getRoute(policyID))}); }); }; From 280a68380ef1e614bf6bf54595e8e9a689efcafc Mon Sep 17 00:00:00 2001 From: war-in Date: Fri, 21 Nov 2025 12:00:26 +0100 Subject: [PATCH 6/7] refactor: move listener up to prevent race conditions --- src/libs/Navigation/Navigation.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/libs/Navigation/Navigation.ts b/src/libs/Navigation/Navigation.ts index 534e0885b8b3..ab47c2f23ac1 100644 --- a/src/libs/Navigation/Navigation.ts +++ b/src/libs/Navigation/Navigation.ts @@ -568,17 +568,19 @@ function getReportRouteByID(reportID?: string, routes: NavigationRoute[] = navig */ const dismissModal = ({ref = navigationRef, callback}: {ref?: NavigationRef; callback?: () => void} = {}) => { isNavigationReady().then(() => { + if (callback) { + const subscription = DeviceEventEmitter.addListener(CONST.MODAL_EVENTS.CLOSED, () => { + subscription.remove(); + callback(); + }); + } + ref.dispatch({type: CONST.NAVIGATION.ACTION_TYPE.DISMISS_MODAL}); // Let React Navigation finish modal transition // eslint-disable-next-line @typescript-eslint/no-deprecated InteractionManager.runAfterInteractions(() => { fireModalDismissed(); }); - - const subscription = DeviceEventEmitter.addListener(CONST.MODAL_EVENTS.CLOSED, () => { - subscription.remove(); - callback?.(); - }); }); }; From 362115b4912ed8a684b21a4298b6a560b8b4f23c Mon Sep 17 00:00:00 2001 From: war-in Date: Fri, 21 Nov 2025 12:00:37 +0100 Subject: [PATCH 7/7] chore: add jsdoc --- src/libs/Navigation/Navigation.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/libs/Navigation/Navigation.ts b/src/libs/Navigation/Navigation.ts index ab47c2f23ac1..3dbb55d6f72b 100644 --- a/src/libs/Navigation/Navigation.ts +++ b/src/libs/Navigation/Navigation.ts @@ -563,6 +563,12 @@ function getReportRouteByID(reportID?: string, routes: NavigationRoute[] = navig /** * Closes the modal navigator (RHP, onboarding). + * + * @param options - Configuration object + * @param options.ref - Navigation ref to use (defaults to navigationRef) + * @param options.callback - Optional callback to execute after the modal has finished closing. + * The callback fires when RightModalNavigator unmounts. + * * For detailed information about dismissing modals, * see the NAVIGATION.md documentation. */