Bug
On Android, a non-modal BottomSheet with an initial index >= 0 intermittently mounts invisible — the component and its children are in the tree, but the sheet is positioned entirely off screen (at window height / closed) and never animates in. It happens most often on a cold app start when the dashboard/screen mounts while the JS thread is busy, so the container onLayout lands a second or two after mount. Under that contention it reproduces ~every time; in lighter conditions it's intermittent.
Root cause
useAnimatedLayout keeps the measured container height in two fields of the layout state: rawContainerHeight (written directly by BottomSheetHostingContainer's onLayout) and containerHeight (the effective value everything else reads). containerHeight is derived from rawContainerHeight by a useAnimatedReaction:
useAnimatedReaction(
() => state.value.rawContainerHeight,
(result, previous) => {
if (result === previous) return;
if (result === INITIAL_LAYOUT_VALUE) return;
state.modify(_state => {
'worklet';
_state.containerHeight = modal ? result - verticalInset : result;
return _state;
});
},
[state, verticalInset, modal]
);
On Reanimated 4 this reaction's handler can miss the initial INITIAL_LAYOUT_VALUE (-999) → measured-height transition (it does not fire for the first layoutState.modify). When that happens, containerHeight stays at the sentinel even though rawContainerHeight is correct.
Consequences cascade from there:
useAnimatedDetents early-exits (if (containerHeight === INITIAL_LAYOUT_VALUE) return {}), so detents is undefined.
isLayoutCalculated never becomes true (it requires normalized detents).
- The mount positioning reaction never runs
animateToPosition / setToPosition, so animatedPosition stays at its initial Dimensions.get('window').height — i.e. fully off screen.
Notably, useDerivedValue consumers of the same layout state do recompute on the modify; only the useAnimatedReaction handler is skipped. That points at a Reanimated-4 reaction-vs-modify timing issue rather than app code.
Captured evidence
Internal logging on a failing Android cold start (custom print added to isLayoutCalculated):
[BottomSheetHostingContainer::handleLayoutEvent] height:832
[isLayoutCalc] containerH=-999 rawContainerH=832 handleH=0 detents=none -> false
rawContainerHeight = 832, but containerHeight is still -999, detents never normalize, and animateToPosition is never called.
Environment
@gorhom/bottom-sheet 5.2.14 (current latest)
react-native-reanimated 4.4.0
react-native-gesture-handler 2.31.1
- React Native 0.81, Android (Samsung A54, Android 14)
- Non-modal
BottomSheet, enableDynamicSizing={false}, snapPoints={[<px>, '100%']}, initial index={0}
Reproduction
Mount a non-modal BottomSheet with index={0} as part of a screen that also kicks off heavy synchronous JS work at mount (so the container onLayout is delayed). On Android the sheet is frequently invisible until something forces a re-layout.
Note: animateOnMount={false} is not a workaround — it makes it worse: it initializes didAnimateOnMount = true, and the layout-ready branch that calls setToPosition is gated on !didAnimateOnMount, so the sheet is never positioned at all (and snapToIndex(0) early-exits because index === nextIndex).
Proposed fix
Seed a non-sentinel containerHeight in the hosting container's layout handler, in the same modify that writes rawContainerHeight, only while it is still the sentinel. The existing reaction keeps refining it (e.g. modal vertical inset) whenever it does run, so normal behavior is unchanged. PR attached.
Bug
On Android, a non-modal
BottomSheetwith an initialindex >= 0intermittently mounts invisible — the component and its children are in the tree, but the sheet is positioned entirely off screen (at window height / closed) and never animates in. It happens most often on a cold app start when the dashboard/screen mounts while the JS thread is busy, so the containeronLayoutlands a second or two after mount. Under that contention it reproduces ~every time; in lighter conditions it's intermittent.Root cause
useAnimatedLayoutkeeps the measured container height in two fields of the layout state:rawContainerHeight(written directly byBottomSheetHostingContainer'sonLayout) andcontainerHeight(the effective value everything else reads).containerHeightis derived fromrawContainerHeightby auseAnimatedReaction:On Reanimated 4 this reaction's handler can miss the initial
INITIAL_LAYOUT_VALUE (-999)→ measured-height transition (it does not fire for the firstlayoutState.modify). When that happens,containerHeightstays at the sentinel even thoughrawContainerHeightis correct.Consequences cascade from there:
useAnimatedDetentsearly-exits (if (containerHeight === INITIAL_LAYOUT_VALUE) return {}), sodetentsisundefined.isLayoutCalculatednever becomestrue(it requires normalized detents).animateToPosition/setToPosition, soanimatedPositionstays at its initialDimensions.get('window').height— i.e. fully off screen.Notably,
useDerivedValueconsumers of the same layout state do recompute on themodify; only theuseAnimatedReactionhandler is skipped. That points at a Reanimated-4 reaction-vs-modifytiming issue rather than app code.Captured evidence
Internal logging on a failing Android cold start (custom
printadded toisLayoutCalculated):rawContainerHeight = 832, butcontainerHeightis still-999, detents never normalize, andanimateToPositionis never called.Environment
@gorhom/bottom-sheet5.2.14 (current latest)react-native-reanimated4.4.0react-native-gesture-handler2.31.1BottomSheet,enableDynamicSizing={false},snapPoints={[<px>, '100%']}, initialindex={0}Reproduction
Mount a non-modal
BottomSheetwithindex={0}as part of a screen that also kicks off heavy synchronous JS work at mount (so the containeronLayoutis delayed). On Android the sheet is frequently invisible until something forces a re-layout.Proposed fix
Seed a non-sentinel
containerHeightin the hosting container's layout handler, in the samemodifythat writesrawContainerHeight, only while it is still the sentinel. The existing reaction keeps refining it (e.g. modal vertical inset) whenever it does run, so normal behavior is unchanged. PR attached.