Summary
BottomSheetModal's unmount() calls the user-provided onDismiss callback unconditionally, even when the modal was never actually presented/mounted. Calling ref.current?.dismiss() on a modal that was never present()-ed therefore fires onDismiss.
Where
src/components/bottomSheetModal/BottomSheetModal.tsx, in unmount():
const hadReactMount = mountRef.current;
// ...
// unmount the node, if sheet is still mounted in React state
if (hadReactMount) {
setState(INITIAL_STATE);
}
// fire `onDismiss` callback
if (_providedOnDismiss) {
_providedOnDismiss(); // <-- fires even when hadReactMount === false
}
The code already computes hadReactMount and uses it to gate the React unmount (setState), but the onDismiss callback is fired without the same guard. This is internally inconsistent: onDismiss semantically means "a presented sheet was dismissed", yet it fires for a sheet that was never mounted.
Reproduction
- Render a
BottomSheetModal permanently (kept mounted, never call present()).
- Call
ref.current?.dismiss() (e.g. from an effect that hides the sheet when a condition is false).
onDismiss fires even though the sheet was never shown.
This bites consumers that perform side effects in onDismiss (e.g. navigation.goBack()): a phantom dismiss on mount navigates the user away. We hit this on react-native@0.85 / new architecture, where dismiss() on a never-presented permanently-mounted modal triggers the unconditional onDismiss.
Suggested fix
Gate the callback on the already-computed hadReactMount, mirroring the setState guard:
if (hadReactMount && _providedOnDismiss) {
_providedOnDismiss();
}
Happy to open a PR (have one ready).
Environment
@gorhom/bottom-sheet: 5.2.14 (same code on current master)
react-native: 0.85.x (new architecture)
react: 19
Summary
BottomSheetModal'sunmount()calls the user-providedonDismisscallback unconditionally, even when the modal was never actually presented/mounted. Callingref.current?.dismiss()on a modal that was neverpresent()-ed therefore firesonDismiss.Where
src/components/bottomSheetModal/BottomSheetModal.tsx, inunmount():The code already computes
hadReactMountand uses it to gate the React unmount (setState), but theonDismisscallback is fired without the same guard. This is internally inconsistent:onDismisssemantically means "a presented sheet was dismissed", yet it fires for a sheet that was never mounted.Reproduction
BottomSheetModalpermanently (kept mounted, never callpresent()).ref.current?.dismiss()(e.g. from an effect that hides the sheet when a condition is false).onDismissfires even though the sheet was never shown.This bites consumers that perform side effects in
onDismiss(e.g.navigation.goBack()): a phantom dismiss on mount navigates the user away. We hit this onreact-native@0.85/ new architecture, wheredismiss()on a never-presented permanently-mounted modal triggers the unconditionalonDismiss.Suggested fix
Gate the callback on the already-computed
hadReactMount, mirroring thesetStateguard:Happy to open a PR (have one ready).
Environment
@gorhom/bottom-sheet: 5.2.14 (same code on currentmaster)react-native: 0.85.x (new architecture)react: 19