diff --git a/src/components/Search/FilterDropdowns/FilterPopupButton.tsx b/src/components/Search/FilterDropdowns/FilterPopupButton.tsx index 48394d882652..a8d54e39ec46 100644 --- a/src/components/Search/FilterDropdowns/FilterPopupButton.tsx +++ b/src/components/Search/FilterDropdowns/FilterPopupButton.tsx @@ -65,6 +65,10 @@ function FilterPopupButton({viewportOffsetTop, popoverWidth, wrapperStyle, popov const triggerRef = useRef(null); const anchorRef = useRef(null); const [isOverlayVisible, setIsOverlayVisible] = useState(false); + // Defer mounting the (potentially heavy) popover content until the dropdown is first opened, then keep it + // mounted so the close animation and reopening stay instant. The content is otherwise mounted eagerly on + // screen focus even while hidden, which runs each filter selector's expensive option-building on page load. + const [hasEverExpanded, setHasEverExpanded] = useState(false); const [customPopoverWidth, setCustomPopoverWidth] = useState(undefined); const {calculatePopoverPosition} = usePopoverPosition(); @@ -90,6 +94,11 @@ function FilterPopupButton({viewportOffsetTop, popoverWidth, wrapperStyle, popov const calculatePopoverPositionAndToggleOverlay = () => { calculatePopoverPosition(anchorRef, popoverAnchorAlignment).then((position) => { setPopoverTriggerPosition({...position, vertical: position.vertical}); + // Latch in the same batch as the open (and only when it will actually open, mirroring toggleOverlay's + // alert-modal guard) so the deferred subtree mounts together with the popover becoming visible. + if (!isOverlayVisible && !willAlertModalBecomeVisible) { + setHasEverExpanded(true); + } toggleOverlay(); }); }; @@ -107,8 +116,9 @@ function FilterPopupButton({viewportOffsetTop, popoverWidth, wrapperStyle, popov {/* Dropdown Trigger */} {renderButton({ref: triggerRef, onPress: calculatePopoverPositionAndToggleOverlay, isExpanded: isOverlayVisible})} - {/* Dropdown overlay */} - {isFocused && ( + {/* Dropdown overlay. Gated on hasEverExpanded so the (potentially heavy) content subtree isn't mounted + until the dropdown is first opened — PopoverWithMeasuredContentBase mounts children even while hidden. */} + {isFocused && hasEverExpanded && (