Skip to content

[Android][Fabric] Surface-killing "Unable to find viewState" crash: the sheet's flattenable ancestor wrappers unflatten under portal churn without a Create mutation #78

Description

@LeCiel

Summary

On Android (New Architecture), rapidly remounting a ModalBottomSheet (reopening a sheet while the previous instance is still tearing down) can kill the whole React surface with:

com.facebook.react.bridge.RetryableMountingLayerException: Unable to find viewState for tag <N>. Surface stopped: false
  at com.facebook.react.fabric.mounting.SurfaceMountingManager.getViewState(SurfaceMountingManager.kt:1057)
  at com.facebook.react.fabric.mounting.SurfaceMountingManager.addViewAt(SurfaceMountingManager.kt:292)
  at com.facebook.react.fabric.mounting.mountitems.IntBufferBatchMountItem.execute(IntBufferBatchMountItem.kt:122)

Because the failure is in the batch-mount path (only view commands are retried), React Native rethrows and destroys the ReactHost: the app is left on a dead white screen (process alive, touches dead) or hard-crashes if it happens inside expo-updates' error-recovery window. In our production app this reached ~2% of Android sessions once a high-frequency action sheet moved onto the portal path.

Root cause

Three ancestor wrappers of the native BottomSheetView are layout-only views that Fabric flattens:

  1. BottomSheetProvider's PortalHost entry wrapper: <View style={StyleSheet.absoluteFill} pointerEvents="box-none">
  2. BottomSheet's outer wrapper: <View style={absoluteFill} pointerEvents={modal ? (isCollapsed ? 'none' : 'auto') : 'box-none'}>
  3. BottomSheet's inner wrapper: <View pointerEvents="box-none" style={absoluteFill}>

Wrapper 2's pointerEvents flips 'none' ↔ 'auto' on every open/collapse, which forces Fabric to unflatten it mid-churn. Under rapid portal entry remount, the resulting reparenting batch can arrive without the wrapper's Create mutation (an RN Fabric differ bug this shape exposes — being reported to facebook/react-native separately). Captured mount batch at crash time, with the surface registry identifying every tag:

REMOVE [2180]->[14] @3     // 2180 = BottomSheetView, 14 = provider-level parent
INSERT [2184]->[14] @3     // 2184 = the entry wrapper — has NO viewState (no Create!)
INSERT [2180]->[2184] @0   // existing sheet reparented into the wrapper
UPDATE LAYOUT [2184]->[14]: x:0 y:0 w:1080 h:2316

Causal confirmation: after pinning wrapper 1 with collapsable={false}, the identical crash moved exactly one level down to wrapper 2 (REMOVE [3010]->[3016], INSERT [3014]->[3016], INSERT [3010]->[3014] — 3016 now a real view, 3014 the outer sheet wrapper). Pinning all three eliminated it: 40 rapid-fire reproductions before the fix reproduced within ~3 attempts, zero after.

Notably the content views below the native view already set collapsable={false} — only the ancestors were left flattenable.

Repro sketch

  • Android, New Architecture, RN 0.85.x, library 0.15.3 (the wrappers are unchanged on current main).
  • A ModalBottomSheet opened from a list row's overflow menu (our case: an action sheet).
  • Open the sheet, trigger an action that closes it and re-renders the host list, and reopen the sheet before the previous instance finishes tearing down. Fast repeated open/close of the same sheet component is the reliable trigger; on a Galaxy S23 it reproduced within ~3 attempts.

Fix

collapsable={false} on the three wrappers (Android-only semantics, no-op on iOS): never flattened means never unflattened, so the buggy mutation sequence cannot be emitted. PR attached.

Environment

  • @swmansion/react-native-bottom-sheet 0.15.3 (wrapper shape identical on main / 0.16.x)
  • react-native 0.85.3, New Architecture (bridgeless), Expo SDK 56
  • Android 13–17, reproduced on Galaxy S23 Ultra; production telemetry across Samsung/Pixel/Motorola/OPPO

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions