From 67f783b4568469051cfb2f2725b667916e3d6cdf Mon Sep 17 00:00:00 2001 From: Rory Abraham <47436092+roryabraham@users.noreply.github.com> Date: Tue, 4 Jun 2024 17:20:21 -0700 Subject: [PATCH] Merge pull request #43074 from Expensify/Rory-UseResponsiveLayoutCurrentNavigator Search bottom-up if a component is in a narrow modal navigator (cherry picked from commit 3c04b02d275b70649790598c0bbbc05fa18bfc63) --- src/hooks/useResponsiveLayout.ts | 24 +++++++++++---- src/hooks/useRootNavigationState.ts | 30 ------------------- .../Navigators/LeftModalNavigator.tsx | 7 +++-- .../Navigators/RightModalNavigator.tsx | 7 +++-- src/libs/Navigation/Navigation.ts | 14 +-------- 5 files changed, 29 insertions(+), 53 deletions(-) delete mode 100644 src/hooks/useRootNavigationState.ts diff --git a/src/hooks/useResponsiveLayout.ts b/src/hooks/useResponsiveLayout.ts index 18b29653479c..a26d50bc56b9 100644 --- a/src/hooks/useResponsiveLayout.ts +++ b/src/hooks/useResponsiveLayout.ts @@ -1,8 +1,8 @@ -import {useContext} from 'react'; +import {NavigationContainerRefContext, NavigationContext} from '@react-navigation/native'; +import {useContext, useMemo} from 'react'; import ModalContext from '@components/Modal/ModalContext'; -import Navigation from '@libs/Navigation/Navigation'; import CONST from '@src/CONST'; -import useRootNavigationState from './useRootNavigationState'; +import NAVIGATORS from '@src/NAVIGATORS'; import useWindowDimensions from './useWindowDimensions'; type ResponsiveLayoutResult = { @@ -38,8 +38,20 @@ export default function useResponsiveLayout(): ResponsiveLayoutResult { // This means it will only be defined if the component calling this hook is a child of a modal component. See BaseModal for the provider. const {activeModalType} = useContext(ModalContext); - // This refers to the state of the root navigator, and is true if and only if the topmost navigator is the "left modal navigator" or the "right modal navigator" - const isDisplayedInModalNavigator = !!useRootNavigationState(Navigation.isModalNavigatorActive); + // We are using these contexts directly instead of useNavigation/useNavigationState, because those will throw an error if used outside a navigator. + // This hook can be used within or outside a navigator, so using useNavigationState does not work. + // Furthermore, wrapping useNavigationState in a try/catch does not work either, because that breaks the rules of hooks. + // Note that these three lines are copied closely from the internal implementation of useNavigation: https://github.com/react-navigation/react-navigation/blob/52a3234b7aaf4d4fcc9c0155f44f3ea2233f0f40/packages/core/src/useNavigation.tsx#L18-L28 + const navigationContainerRef = useContext(NavigationContainerRefContext); + const navigator = useContext(NavigationContext); + const currentNavigator = navigator ?? navigationContainerRef; + + const isDisplayedInNarrowModalNavigator = useMemo( + () => + !!currentNavigator?.getParent?.(NAVIGATORS.RIGHT_MODAL_NAVIGATOR as unknown as undefined) || + !!currentNavigator?.getParent?.(NAVIGATORS.LEFT_MODAL_NAVIGATOR as unknown as undefined), + [currentNavigator], + ); // The component calling this hook is in a "narrow pane modal" if: const isInNarrowPaneModal = @@ -47,7 +59,7 @@ export default function useResponsiveLayout(): ResponsiveLayoutResult { activeModalType === CONST.MODAL.MODAL_TYPE.RIGHT_DOCKED || // or there's a "right modal navigator" or "left modal navigator" on the top of the root navigation stack // and the component calling this hook is not the child of another modal type, such as a confirm modal - (isDisplayedInModalNavigator && !activeModalType); + (isDisplayedInNarrowModalNavigator && !activeModalType); const shouldUseNarrowLayout = isSmallScreenWidth || isInNarrowPaneModal; diff --git a/src/hooks/useRootNavigationState.ts b/src/hooks/useRootNavigationState.ts deleted file mode 100644 index 3f03e1ceb253..000000000000 --- a/src/hooks/useRootNavigationState.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type {EventListenerCallback, NavigationContainerEventMap} from '@react-navigation/native'; -import type {NavigationState} from '@react-navigation/routers'; -import {useCallback, useSyncExternalStore} from 'react'; -import {navigationRef} from '@libs/Navigation/Navigation'; - -/** - * This hook is a replacement for `useNavigationState` for nested navigators. - * If `useNavigationState` is used within a nested navigator then the state that's returned is the state of the nearest parent navigator, - * not the root navigator state representing the whole app's navigation tree. - * - * Use with caution, because re-rendering any component every time the root navigation state changes can be very costly for performance. - * That's why the selector is mandatory. - */ -function useRootNavigationState(selector: (state: NavigationState) => T): T | undefined { - const getSnapshot = useCallback(() => { - if (!navigationRef?.current) { - return; - } - return selector(navigationRef.current.getRootState()); - }, [selector]); - - const subscribeToRootState = useCallback((callback: EventListenerCallback) => { - const unsubscribe = navigationRef?.current?.addListener('state', callback); - return () => unsubscribe?.(); - }, []); - - return useSyncExternalStore(subscribeToRootState, getSnapshot); -} - -export default useRootNavigationState; diff --git a/src/libs/Navigation/AppNavigator/Navigators/LeftModalNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/LeftModalNavigator.tsx index 43241a431c32..b1cfb8e5d9a1 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/LeftModalNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/LeftModalNavigator.tsx @@ -7,7 +7,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import ModalNavigatorScreenOptions from '@libs/Navigation/AppNavigator/ModalNavigatorScreenOptions'; import type {AuthScreensParamList, LeftModalNavigatorParamList} from '@libs/Navigation/types'; -import type NAVIGATORS from '@src/NAVIGATORS'; +import NAVIGATORS from '@src/NAVIGATORS'; import SCREENS from '@src/SCREENS'; import Overlay from './Overlay'; @@ -32,7 +32,10 @@ function LeftModalNavigator({navigation}: LeftModalNavigatorProps) { /> )} - + )} - +