Skip to content

Commit 2df430a

Browse files
committed
fix(Popover): replace popstate listener with React Navigation route-key subscription
1 parent 824f3c0 commit 2df430a

2 files changed

Lines changed: 33 additions & 32 deletions

File tree

src/components/DatePicker/DatePickerModal.tsx

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import PopoverWithMeasuredContent from '@components/PopoverWithMeasuredContent';
55
import useBottomSafeSafeAreaPaddingStyle from '@hooks/useBottomSafeSafeAreaPaddingStyle';
66
import useResponsiveLayout from '@hooks/useResponsiveLayout';
77
import useThemeStyles from '@hooks/useThemeStyles';
8-
import getPlatform from '@libs/getPlatform';
98
import {setDraftValues} from '@userActions/FormActions';
109
import CONST from '@src/CONST';
1110
import CalendarPicker from './CalendarPicker';
@@ -49,28 +48,6 @@ function DatePickerModal({
4948
// eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth
5049
const {isSmallScreenWidth} = useResponsiveLayout();
5150

52-
useEffect(() => {
53-
if (
54-
getPlatform() !== CONST.PLATFORM.WEB ||
55-
!isSmallScreenWidth ||
56-
!isVisible ||
57-
!shouldCloseWhenBrowserNavigationChanged ||
58-
typeof window === 'undefined' ||
59-
typeof window.addEventListener !== 'function'
60-
) {
61-
return;
62-
}
63-
64-
const listener = () => {
65-
onClose?.();
66-
};
67-
68-
window.addEventListener('popstate', listener);
69-
return () => {
70-
window.removeEventListener('popstate', listener);
71-
};
72-
}, [isSmallScreenWidth, isVisible, onClose, shouldCloseWhenBrowserNavigationChanged]);
73-
7451
useEffect(() => {
7552
if (shouldSaveDraft && formID) {
7653
setDraftValues(formID, {[inputID]: selectedDate});

src/components/Popover/index.tsx

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import {usePopoverActions, usePopoverState} from '@components/PopoverProvider';
55
import PopoverWithoutOverlay from '@components/PopoverWithoutOverlay';
66
import useResponsiveLayout from '@hooks/useResponsiveLayout';
77
import useSidePanelState from '@hooks/useSidePanelState';
8+
import subscribeToRootNavigation from '@libs/Navigation/helpers/subscribeToRootNavigation';
9+
import Navigation from '@libs/Navigation/Navigation';
10+
import navigationRef from '@libs/Navigation/navigationRef';
811
import TooltipRefManager from '@libs/TooltipRefManager';
912
import CONST from '@src/CONST';
1013
import type PopoverProps from './types';
@@ -55,20 +58,41 @@ function Popover(props: PopoverProps) {
5558
React.useEffect(() => {
5659
// When this Popover manages its own back-guard (`shouldHandleNavigationBack`), the Modal-level
5760
// history sync (useSyncModalWithHistory) closes it on browser Back and consumes the entry. This
58-
// listener only covers the other case: dismissing the popover when the browser navigation changes
59-
// for some other reason, without intercepting that navigation.
60-
if (!shouldCloseWhenBrowserNavigationChanged || props.shouldHandleNavigationBack) {
61+
// listener only covers the other case: dismissing the popover when the active navigation route
62+
// changes, without intercepting that navigation.
63+
//
64+
// We subscribe to React Navigation state events rather than raw `popstate` so that
65+
// `navigationRef.getCurrentRoute()` is already fresh when the callback fires. Sentinel-only
66+
// history changes (e.g. a nested YearPickerModal opening/closing) do NOT change the focused
67+
// route key, so the calendar popover stays open. A real navigation away changes the key and
68+
// closes the popover.
69+
if (!shouldCloseWhenBrowserNavigationChanged || props.shouldHandleNavigationBack || !isVisible) {
6170
return;
6271
}
63-
const listener = () => {
64-
if (!isVisible) {
72+
73+
let isActive = true;
74+
let baselineKey: string | undefined;
75+
// Holds the unsubscribe function once the subscription is set up asynchronously.
76+
const unsubscribeRef: {current: (() => void) | undefined} = {current: undefined};
77+
78+
Navigation.isNavigationReady().then(() => {
79+
if (!isActive) {
6580
return;
6681
}
67-
onClose?.();
68-
};
69-
window.addEventListener('popstate', listener);
82+
baselineKey = navigationRef.getCurrentRoute()?.key;
83+
unsubscribeRef.current = subscribeToRootNavigation(() => {
84+
if (!isActive || baselineKey === undefined) {
85+
return;
86+
}
87+
if (navigationRef.getCurrentRoute()?.key !== baselineKey) {
88+
onClose?.();
89+
}
90+
});
91+
});
92+
7093
return () => {
71-
window.removeEventListener('popstate', listener);
94+
isActive = false;
95+
unsubscribeRef.current?.();
7296
};
7397
}, [onClose, isVisible, shouldCloseWhenBrowserNavigationChanged, props.shouldHandleNavigationBack]);
7498

0 commit comments

Comments
 (0)