react-native-bottom-sheet icon indicating copy to clipboard operation
react-native-bottom-sheet copied to clipboard

[v4] Bottom sheet is not dismissed if `dismiss` is called soon after `present`

Open augustebr opened this issue 1 year ago • 16 comments

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

  1. Install app: npm install
  2. Run the app: npx expo start
  3. Press twice on the button in quick succession
  4. Observe the bottom sheet remain open, when it should be dismissed

Describe what you expected to happen:

  1. 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',
  },
});

augustebr avatar Sep 18 '24 08:09 augustebr

same problem here

mohamedziadjabbad avatar Oct 01 '24 16:10 mohamedziadjabbad

this should be fixed with the next alpha releases of v5

gorhom avatar Oct 02 '24 19:10 gorhom

https://github.com/user-attachments/assets/88a30cef-688e-47d8-98a0-122a2454ceb5

gorhom avatar Oct 02 '24 19:10 gorhom

@gorhom when are you planning to release alpha?

yuliahey avatar Nov 21 '24 13:11 yuliahey

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 avatar Dec 05 '24 08:12 borysenko-oleksandr

@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.

efstathiosntonas avatar Dec 05 '24 10:12 efstathiosntonas

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>
  )
}

PhilipGrefe avatar Jan 19 '25 20:01 PhilipGrefe

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 avatar Feb 08 '25 13:02 celian-riboulet

@celian-riboulet woah,, that is bad ,, working on a fix

gorhom avatar Feb 08 '25 19:02 gorhom

@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 avatar Feb 08 '25 20:02 gorhom

@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 :).

celian-riboulet avatar Feb 09 '25 15:02 celian-riboulet

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

AlixH avatar Feb 12 '25 18:02 AlixH

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.

joprice avatar Feb 27 '25 22:02 joprice

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 ?

guillaume-frdt avatar Mar 08 '25 18:03 guillaume-frdt

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]);

mantu-bit avatar Mar 12 '25 12:03 mantu-bit

onDismiss does not work properly with reduce motion: on in version 5.1.6. Its seems like its not firing at all.

erkanerk avatar Jul 22 '25 09:07 erkanerk