Skip to content

fix: keep unmeasured modal container height unmeasured when insets are set - #2786

Open
vanxh wants to merge 1 commit into
gorhom:masterfrom
vanxh:fix/unmeasured-modal-host-inset
Open

vanxh wants to merge 1 commit into
gorhom:masterfrom
vanxh:fix/unmeasured-modal-host-inset

Conversation

@vanxh

@vanxh vanxh commented Oct 19, 2026

Copy link
Copy Markdown

Motivation

useAnimatedLayout seeds containerHeight from the provider's container layout. When that container has not been measured yet, its height is still INITIAL_LAYOUT_VALUE (-999), but for a modal with insets the initial state subtracts them anyway:

_state.containerHeight = modal
  ? containerLayout.height - verticalInset // -999 - 62 = -1061
  : containerLayout.height;

-1061 is no longer equal to INITIAL_LAYOUT_VALUE, so isLayoutCalculated and useAnimatedDetents treat it as a real height. With dynamic sizing the detents become [0], animatedSheetHeight goes negative, and the mount animation starts from those values. When the real height arrives a few ms later the detents correct themselves, but the sheet is left unpainted, with only the handle visible.

It only happens when the sheet mounts before the BottomSheetModalProvider container has laid out, so it is timing dependent. We hit it with a provider mounted next to the sheet (the sheet is the whole UI of a transparentModal route) with topInset={insets.top} and dynamic sizing. Presenting while the JS thread was busy reproduced it on most attempts on iOS (26/27, new architecture). Logging animatedLayoutState and animatedDetentsState through useBottomSheetInternal showed every blank sheet starting with containerHeight: -1061, detents: [0] before 812, [346.33], and every working one starting at 812.

The fix leaves containerHeight at INITIAL_LAYOUT_VALUE until the container is measured. The existing reactions already skip INITIAL_LAYOUT_VALUE and apply the insets once the real height arrives, so a measured container behaves exactly as before. With the guard, the same flow rendered 8 of 8 times; without it, the blank sheet reproduced on most attempts.

Minimal setup:

function RouteSheet({ children }) {
  const ref = useRef<BottomSheetModal>(null);
  const insets = useSafeAreaInsets();
  useEffect(() => ref.current?.present(), []);
  return (
    <BottomSheetModalProvider>
      <BottomSheetModal ref={ref} topInset={insets.top} enableDynamicSizing>
        <BottomSheetView>{children}</BottomSheetView>
      </BottomSheetModal>
    </BottomSheetModalProvider>
  );
}

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant