react-native-bottom-sheet: [v5] Modal reopens while opening second, and while dismissing first modal

Bug

Maybe similiar to https://github.com/gorhom/react-native-bottom-sheet/issues/204 I have 2 modals and global state. When global state updates i dismiss first modal and open second modal

  1. If i dismiss first modal, then: second modal reopens while opening

https://github.com/gorhom/react-native-bottom-sheet/assets/99968085/85e760d6-c800-47f5-a4bf-983c9dab2a2b

  1. If i close first modal, then: second modal opens fine but if i close second modal, first modal reopens. (or is even not closed?)

https://github.com/gorhom/react-native-bottom-sheet/assets/99968085/c653b295-c0c4-4907-83bf-e1f020cd38b3

Environment info

Library Version
@gorhom/bottom-sheet ^5.0.0-alpha.3
react-native 0.72.5
react-native-reanimated ^3.5.1
react-native-gesture-handler ~2.12.0

Steps To Reproduce

  1. Create 2 modals in 2 component
  2. Open first modal
  3. Close first modal and present second modal

Describe what you expected to happen:

  1. When I dismiss first modal and present second modal
  2. Second modal wont reopen

Reproducible sample code

I thought that the modal is reopened because the global state changes, but this is not the case: while opening the second modal and closing the first modal, the second modal still reopens

const presentBottomSheet = useCallback(() => {
bottomSheetModalRef.current?.present();
}, []);

const closeBottomSheet = useCallback(() => {
bottomSheetModalRef.current?.dismiss();
}, []);

const presentTeamBottomSheet = useCallback(() => {
bottomSheetModalRef.current?.dismiss();
teamBottomSheetModalRef.current?.present();
}, []);

const closeTeamBottomSheet = useCallback(() => {
teamBottomSheetModalRef.current?.dismiss();
}, []);

return <>
...
<BottomSheetModal
		ref={bottomSheetModalRef}
		index={0}
		snapPoints={snapPoints}
		backdropComponent={renderBackdrop}
		handleComponent={null}
		enableOverDrag={false}
		enablePanDownToClose={false}>
		<BottomSheetView
			style={{
				flex: 1,
			}}>
			<View style={styles.header}>
				<Text style={styles.name}>Список участников</Text>
				<Pressable style={styles.close} onPress={closeBottomSheet}>
					<View style={styles.icon}>
						<Icons.Close
							color={colors.gray400}
							width={scale.lg}
							height={scale.lg}
						/>
					</View>
				</Pressable>
			</View>
			<ScrollView
				contentContainerStyle={{
					paddingBottom: insets.bottom + scale.md,
					marginHorizontal: scale.md,
				}}>
				{teams.map((team) => {
					return (
						<Pressable
							key={team.id}
							style={styles.item}
							onPress={() => {
								presentTeamBottomSheet();
							}}>
							<TeamItem team={team} />
						</Pressable>
					);
				})}
			</ScrollView>
		</BottomSheetView>
	</BottomSheetModal>
	<BottomSheetModal
		ref={teamBottomSheetModalRef}
		snapPoints={animatedSnapPoints as any}
		contentHeight={animatedContentHeight}
		handleHeight={animatedHandleHeight}
		backdropComponent={renderTeamBackdrop}
		bottomInset={scale.md + insets.bottom}
		handleComponent={null}
		enableOverDrag={false}
		enablePanDownToClose={false}>
		<BottomSheetView
			style={{
				flex: 1,
			}}
			onLayout={handleContentLayout}>
			<View style={styles.header}>
				<Text style={styles.name}>{t('participateToOrder')}</Text>
				<Pressable style={styles.close} onPress={closeTeamBottomSheet}>
					<View style={styles.icon}>
						<Icons.Close
							color={colors.gray400}
							width={scale.lg}
							height={scale.lg}
						/>
					</View>
				</Pressable>
			</View>
			<View style={styles.contentContainer}>
				<Text>Some longh text about team round</Text>
				<Button label={t('joinToOrder')} onPress={joinToOrder} />
			</View>
		</BottomSheetView>
	</BottomSheetModal>
</>

About this issue

  • Original URL
  • State: open
  • Created 9 months ago
  • Reactions: 5
  • Comments: 22

Most upvoted comments

the bug still exists

my workaround is…

  1. Create a custom component called Sheet to use for all bottom sheets in my app. (see code below)
  2. manage the close state from a parent component. So if the modal closes, the parent component sets a state variable sheetOpen to false.
  3. If sheetOpen is false, don’t render the modal. This ensures there aren’t other instances of the Sheet opening. if (!sheetOpen) { return null; }

The downside to this approach is that you don’t get the animation of the modal closing when it switches to sheetOpen=false. I would love to use a better solution if someone has one!! This is a work around.

const Sheet = ({
  sheetOpen,
  setSheetOpen,
  bottomSheetProps,
  children,
  onSheetClose,
  footerComponent,
}: {
  sheetOpen: boolean;
  setSheetOpen: React.Dispatch<React.SetStateAction<boolean>>;
  bottomSheetProps?: BottomSheetProps;
  children?: React.ReactNode;
  onSheetClose?: () => void;
  footerComponent?: React.FC<BottomSheetFooterProps>;
}) => {
  const bottomSheetRef = useRef<BottomSheet>(null);

  // Snap points
  const snapPoints = useMemo(() => ["80%"], []);

  // Handle sheet state changes
  const handleSheetChanges = useCallback(
    (index: number) => {
      if (index < 0) {
        setSheetOpen(false);
        Keyboard.dismiss();
        onSheetClose && onSheetClose();
      } else {
        setSheetOpen(true);
      }
    },
    [setSheetOpen, onSheetClose]
  );

  // Sync the sheet open state with the component's prop
  useEffect(() => {
    if (sheetOpen) {
      bottomSheetRef.current?.expand();
    } else {
      bottomSheetRef.current?.close();
    }
  }, [sheetOpen]);

  // Bottom sheet has a bug where another modal will open after closing one, so this is a workaround
  // to not render the modal at all if it's not open.
  // In order for this to work, the initial index had to be set to 0, which is the open state. -1 is closed.
  if (!sheetOpen) {
    return null;
  }
  return (
    <Portal>
      <BottomSheet
        footerComponent={footerComponent}
        ref={bottomSheetRef}
        index={0}
        snapPoints={snapPoints}
        onChange={handleSheetChanges}
        enablePanDownToClose={true}
        backgroundStyle={{
          borderTopRightRadius: 30,
          borderTopLeftRadius: 30,
          backgroundColor: "#f8f6ff",
        }}
        {...bottomSheetProps}
      >
        <View style={styles.contentContainer}>{children}</View>
      </BottomSheet>
    </Portal>
  );
};
    <Sheet
      sheetOpen={sheetOpen}
      setSheetOpen={setSheetOpen}
      bottomSheetProps={{
        snapPoints: ["70%"],
        backgroundStyle: {
          backgroundColor: "#5423E7",
        },
        children: <></>,
      }}
      key={"recommend-sheet"}
    >
     put stuff in the sheet here
    </Sheet>```
    
    setSheetOpen is a function to set state and sheetOpen is the boolean from useState

Same problem in v4.5.1.

  1. Close the first modal and open the second modal
  2. Close the second modal, the first modal pops up automatically

My temporary solution is to add a delay function in the middle :

xxx.current?.close();

await delay(600);

xxx.current?.present();