Skip to content

[Bug]: BottomSheetModal keeps the previous orientation's topInset after rotation, pushing the footer off-screen #2793

Open

Description

@abisalde

Version

v5

Reanimated Version

v3

Gesture Handler Version

v2

Platforms

iOS

What happened?

The template's dropdowns don't list Reanimated 4 or Gesture Handler 3 yet. Actual versions: @gorhom/bottom-sheet 5.2.14, Reanimated 4.7.1, react-native-worklets 0.13.0, Gesture Handler 3.3.0, Expo SDK 58, React Native 0.88.1 (New Architecture).

When the device rotates while a BottomSheetModal with topInset is open, animatedLayoutState.containerHeight keeps the inset from the previous orientation:

Rotation (iPhone 17 Pro, topInset={insets.top}) Expected containerHeight Actual Effect
landscape → portrait (inset 0 → 62) 812 (874 − 62) 874 footer pushed 62pt below the screen edge
portrait → landscape (inset 62 → 0) 402 340 footer floats 62pt above the bottom

Closing and reopening the sheet in the new orientation always gives the right height. Any modal sheet whose topInset comes from the safe area is affected. It is timing dependent: now and then the first rotation after launch comes out right, and repeating the steps reproduces it.

Video (iPhone 17 Pro simulator, stock 5.2.14, both directions): https://github.com/abisalde/gorhom-sheet-rotation-repro/blob/main/media/rotation-bug.mp4

Cause. useAnimatedLayout subtracts topInset + bottomInset in the react callback of its two useAnimatedReactions, but their prepare callbacks return only the raw container height. On rotation the window height and the safe-area inset arrive in separate updates. When the inset changes, the reaction restarts with the new closure. The height it returns is equal to previous, because Reanimated keeps previous across restarts, so it returns early and the new inset is never applied. The [state, verticalInset, modal] dependency array doesn't help, because Reanimated ignores it on native (see #2785).

Suggested fix. Return the inset-adjusted height from prepare, so an inset change on its own produces a new value. Applied to both reactions:

 useAnimatedReaction(
-  () => containerLayoutState?.get().height,
-  (result, previous) => {
-    if (!result || result === previous) {
+  () => {
+    const height = containerLayoutState?.get().height;
+    if (!height || height === INITIAL_LAYOUT_VALUE) {
+      return INITIAL_LAYOUT_VALUE;
+    }
+    return modal ? height - verticalInset : height;
+  },
+  (result, previous) => {
+    if (result === previous) {
       return;
     }
     if (result === INITIAL_LAYOUT_VALUE) {
       return;
     }
     state.modify(_state => {
       'worklet';
-      _state.containerHeight = modal ? result - verticalInset : result;
+      _state.containerHeight = result;
       return _state;
     });
   },
   [state, verticalInset, modal]
 );

The rawContainerHeight reaction above it gets the same change. With this patch, both rotations in the repro give the expected heights. We run it as a pnpm patch in our app and checked it on iOS and Android, including foldables. It doesn't overlap with #2786, which only changes the initial state. Fix: #2794.

Reproduction steps

  • Run the repro on an iPhone with a notch or Dynamic Island, with rotation lock off. The sheet prints useBottomSheetInternal() values.
  • Rotate to landscape.
  • Tap Open sheet.
  • Rotate to portrait.
  • Expected: containerHeight 812, red footer fully visible. Actual: containerHeight 874, footer cut off at the bottom edge.
  • Reverse: close, open in portrait, rotate to landscape. Expected 402, actual 340.

Reproduction sample

https://snack.expo.dev/o9tUM4R_D2_6QhVFzqEzR (SDK 55)

Snack cannot install @gorhom/bottom-sheet >= 5.1.8 from npm: its packager rejects the react-native-reanimated peer range >=3.16.0 || >=4.0.0- as non-semver (HTTP 422). So the Snack includes the published 5.2.14 src folder unchanged as local files, and App.tsx imports from it. Snack's SDK 56 runtime also fails to start in Expo Go 56.0.4 ("Tried to register two views with the same name RNSScreen"), hence SDK 55.

Same code as a standalone Expo SDK 58 / Reanimated 4.7.1 app, with the video: https://github.com/abisalde/gorhom-sheet-rotation-repro

This replaces #2792, which the template check closed because it linked only the repo.

Relevant log output

N/A: no errors; the wrong value is visible in the on-screen readout.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions