[v4] Bottom sheet is not dismissed if `dismiss` is called soon after `present`
Bug
The bottom-sheet is not dismissed when dismiss() is called soon after present(). This happens when these two are called in quick succession. The code in the example demonstrates the issue and here's also a short video of that code in action.
https://github.com/user-attachments/assets/18991be9-47e5-4278-a900-2603d82605a9
Environment info
| Library | Version |
|---|---|
| @gorhom/bottom-sheet | 4.6.4 |
| react-native | 0.74.5 |
| react-native-reanimated | 3.10.1 |
| react-native-gesture-handler | 2.16.2 |
Steps To Reproduce
- Install app:
npm install - Run the app:
npx expo start - Press twice on the button in quick succession
- Observe the bottom sheet remain open, when it should be dismissed
Describe what you expected to happen:
- After double press, the sheet should be dismissed.
Reproducible sample code
import React, { useMemo, useState, useEffect } from 'react';
import { View, Text, StyleSheet, Button } from 'react-native';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { BottomSheetModal, BottomSheetModalProvider } from '@gorhom/bottom-sheet';
export default function App() {
const [isOpen, setIsOpen] = useState(false);
const ref = React.useRef<BottomSheetModal>(null);
const snapPoints = useMemo(() => ['25%', '50%'], []);
useEffect(() => {
if (isOpen) {
ref.current?.present();
} else {
ref.current?.dismiss();
}
}, [isOpen]);
const toggleSheet = () => {
setIsOpen(prevState => !prevState);
};
return (
<GestureHandlerRootView style={styles.container}>
<BottomSheetModalProvider>
<View style={styles.container}>
<Button
title={isOpen ? "Close Sheet" : "Open Sheet"}
onPress={toggleSheet}
/>
<BottomSheetModal
ref={ref}
index={1}
snapPoints={snapPoints}
enablePanDownToClose={true}
onDismiss={() => setIsOpen(false)}
>
<View style={styles.contentContainer}>
<Text>Bottom Sheet Modal Content</Text>
</View>
</BottomSheetModal>
</View>
</BottomSheetModalProvider>
</GestureHandlerRootView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 24,
backgroundColor: 'black',
},
contentContainer: {
flex: 1,
alignItems: 'center',
},
});
same problem here
this should be fixed with the next alpha releases of v5
https://github.com/user-attachments/assets/88a30cef-688e-47d8-98a0-122a2454ceb5
@gorhom when are you planning to release alpha?
This problem seems to be exacerbated when you are in Reduce Motion mode. @gorhom Could you please be more specific about which version this will be fixed in, as it affects us? I can still reproduce it in v5.0.6
@borysenko-oleksandr based on the linked commit, this is included in 5.0.6.
I believe this might introduced another bug, when the bottom sheet modal is closed with .close() the bottom sheet re-shows it self.
EDIT My mistake was to render the bottom sheet conditionally. Probably, that caused the dismiss to not work properly.
Also, I had another situation where a dismiss / dismissAll would not work when a faulty bottom sheet was rendered inside the bottom sheet that I wanted to dismiss.
-- Dismiss / dismissAll also don't work when a modal is opened within another modal. Example: The issue exists for all stack behaviours.
Example:
const NewSheetButton = () => {
const secondSheetRef = useRef<BottomSheetModal>(null)
const handlePress = () => {
secondSheetRef.current?.present()
// within the newly opened bottom sheet modal, dismiss / dismissAll does not work
}
return (
<MyBottomSheetModal ref={secondSheetRef}>
<Button onPress={handlePress}>
<Button.Text>open another bottom sheet</Button.Text>
</Button>
</MyBottomSheetModal>
)
}
The initial reproducible example seems to be fixed (I tested in 5.1.0). But in fact when multiple bottom sheet modals are getting stacked / mounted / unmounted in quick succession, they get stacked together (cf: video)
https://github.com/user-attachments/assets/f49ecb63-1c26-45cf-be6b-9897513f801e
Here is the code from the video:
import {
BottomSheetModal,
BottomSheetModalProvider,
} from '@gorhom/bottom-sheet';
import React, { useEffect, useMemo, useState } from 'react';
import { Button, StyleSheet, Text, View } from 'react-native';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
export default function App() {
const ref1 = React.useRef<BottomSheetModal>(null);
const ref2 = React.useRef<BottomSheetModal>(null);
const [currentBS, setCurrentBS] = useState(0);
const snapPoints = useMemo(() => ['25%', '50%'], []);
useEffect(() => {
if (currentBS === 0) {
ref1.current?.present();
} else {
ref2.current?.present();
}
}, [currentBS]);
return (
<GestureHandlerRootView style={styles.container}>
<BottomSheetModalProvider>
<View style={styles.container}>
<Button title='Open sheet 1' onPress={() => setCurrentBS(0)} />
<Button title='Open sheet 2' onPress={() => setCurrentBS(1)} />
{currentBS === 0 ? (
<BottomSheetModal
key={1}
ref={ref1}
index={1}
snapPoints={snapPoints}
enablePanDownToClose
enableDynamicSizing={false}
>
<View style={styles.contentContainer}>
<Text>Bottom Sheet 1</Text>
</View>
</BottomSheetModal>
) : (
<BottomSheetModal
key={2}
ref={ref2}
index={1}
snapPoints={snapPoints}
enablePanDownToClose
enableDynamicSizing={false}
backgroundStyle={{ backgroundColor: 'red' }}
>
<View style={styles.contentContainer}>
<Text>Bottom Sheet 2</Text>
</View>
</BottomSheetModal>
)}
</View>
</BottomSheetModalProvider>
</GestureHandlerRootView>
);
}
const styles = StyleSheet.create({
container: {
backgroundColor: 'black',
flex: 1,
padding: 24,
},
contentContainer: {
alignItems: 'center',
flex: 1,
},
});
@celian-riboulet woah,, that is bad ,, working on a fix
@celian-riboulet may i know why are you mounting and unmounting the bottom sheet modals? this is causing an internal issue and might take me time to fix it.
You could resolve issue simply by remove this logic
https://github.com/user-attachments/assets/59c06f92-0507-423b-a5eb-6ba95a6ffb31
@gorhom Thank's for having a look!
I'm mounting / unmouting the bottom sheets to reproduce what happens while using the react-navigation-bottom-sheet library.
This library implements a custom react-navigation navigator using bottom sheets.
So when screens are stacked / dismissed, the bottom sheet modals get mounted / unmounted but that triggers the bug in my previous message.
The app i'm working on is using a fork of this library and we are heavily using bottom sheets for almost every screens.
I may also try to have a look because this is starting to become a big issue for my company :).
I have the opposite issue where calling present just after dismiss keeps the modal dismissed. If I do it 200 ms after, I have the issue, if I do it 2 secs after, I don't
I'm noticing something that might be related - I open the modal the first time and the ref is non-null, call dismiss, then on second open, the ref is always null, causing dismiss to be a no-op. I can open a separate issue if it's a different problem.
Update: I found that my issue was accessing the ref in a promise callback. If I de-reference it before the promise, it's not null. So unrelated.
The initial reproducible example seems to be fixed (I tested in 5.1.0). But in fact when multiple bottom sheet modals are getting stacked / mounted / unmounted in quick succession, they get stacked together (cf: video)
Screenshare.-.2025-02-08.2_22_23.PM.mp4 Here is the code from the video:
I'm having the same problem here. Any news ?
Same issue here. I am openening and closing multiple bottomsheetmodals based on the status
useEffect(() => {
console.log("RIDE_STATUS", rideDetails?.rideStatus);
const status = rideDetails?.rideStatus;
switch (status) {
// Show car selection bottom sheet when selecting a car
case DRIVER_STATUS.SELECT_CAR:
carSelectionBottomSheetRef?.current?.present();
break;
// Show nearby driver search bottom sheet when ride is requested
case DRIVER_STATUS.RIDE_REQUESTED:
lookingForNearByDriverBottomSheetRef?.current?.present();
break;
// Handle cases where ride is canceled or no response is received
case DRIVER_STATUS.NO_RESPONSE:
case DRIVER_STATUS.RIDE_CANCELED:
whereToBottomSheetRef?.current?.present();
break;
// Open cancel search bottom sheet
case DRIVER_STATUS.CANCEL_SEARCH:
cancelSearchBottomSheetRef?.current?.present();
break;
// Transition from 'where to' to driver found bottom sheet when ride is accepted
case DRIVER_STATUS.RIDE_ACCEPTED:
whereToBottomSheetRef?.current?.dismiss();
driverFoundBottomSheetRef?.current?.present();
break;
// Show in-ride driver details when ride is active
case DRIVER_STATUS.RIDE_ACTIVE:
inRideDriverDetailsBottomSheetRef?.current?.present();
break;
// Dismiss bottom sheet and reset ride when completed
case DRIVER_STATUS.RIDE_COMPLETED:
whereToBottomSheetRef?.current?.present();
break;
// Handle unexpected ride statuses
default:
console.warn(`Unhandled ride status: ${status}`);
whereToBottomSheetRef?.current?.present();
break;
}
}, [rideDetails, scheduleRideData]);
onDismiss does not work properly with reduce motion: on in version 5.1.6. Its seems like its not firing at all.