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

[v5] Modal reopens while opening second, and while dismissing first modal

Open devoren opened this issue 1 year ago • 28 comments

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>
</>

devoren avatar Oct 05 '23 09:10 devoren

Same issue here, this was fine in v4

enchorb avatar Oct 05 '23 21:10 enchorb

@enchorb yeah same :( iam using v5 because of web support

devoren avatar Oct 15 '23 16:10 devoren

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

Simoon-F avatar Oct 23 '23 05:10 Simoon-F

@Simoon-Fyes, I use delay too, but it looks laggy :(

devoren avatar Oct 23 '23 06:10 devoren

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.

github-actions[bot] avatar Nov 22 '23 09:11 github-actions[bot]

the bug still exists

devoren avatar Nov 22 '23 10:11 devoren

yeap, I confirm, still exists!

Bad-Listener avatar Nov 27 '23 16:11 Bad-Listener

anyone find a work around?

michaelbrant avatar Dec 19 '23 23:12 michaelbrant

I'm using delays now, but it's still buggy

devoren avatar Dec 20 '23 04:12 devoren

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.

github-actions[bot] avatar Jan 19 '24 09:01 github-actions[bot]

the bug still exists

devoren avatar Jan 19 '24 09:01 devoren

any suggestion to solve this problem?

cristiano-linvix avatar Feb 05 '24 19:02 cristiano-linvix

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

michaelbrant avatar Feb 05 '24 19:02 michaelbrant

Same here

flodlc avatar Feb 15 '24 23:02 flodlc

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

This works for me!!!

cristiano-linvix avatar Feb 16 '24 11:02 cristiano-linvix

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.

github-actions[bot] avatar Mar 18 '24 09:03 github-actions[bot]

bug still exists

devoren avatar Mar 18 '24 10:03 devoren

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

Hey, I'm trying to reproduce your workaround. However you didn't provide your imports, could you tell me what is "Portal" ? Also, are you importing "BottomSheet" from "@gorhom/bottom-sheet" ? Because it says this :

"'"@gorhom/bottom-sheet"' has no exported member named 'BottomSheet'. Did you mean 'useBottomSheet'?ts(2724) import BottomSheet"

when I type

import { BottomSheet } from "@gorhom/bottom-sheet";

Thank you.

POLEC4T avatar Apr 03 '24 17:04 POLEC4T

Here are my imports, I'm not sure why BottomSheet isn't resolving for you..

import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
import BottomSheet, { BottomSheetBackdrop, BottomSheetFooterProps, BottomSheetProps } from "@gorhom/bottom-sheet";
import { BottomSheetDefaultBackdropProps } from "@gorhom/bottom-sheet/lib/typescript/components/bottomSheetBackdrop/types";
import { Keyboard, StyleSheet, View } from "react-native";
import { Portal } from "@gorhom/portal";

michaelbrant avatar Apr 03 '24 17:04 michaelbrant

Thank you very much, It was because I was writing "{ BottomSheet }" instead of "BottomSheet"

POLEC4T avatar Apr 03 '24 17:04 POLEC4T

H

Could you please provide an example of a use the Sheet component you created ? Thank you.

POLEC4T avatar Apr 03 '24 17:04 POLEC4T

    <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

michaelbrant avatar Apr 03 '24 17:04 michaelbrant

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.

github-actions[bot] avatar May 04 '24 09:05 github-actions[bot]

bug still exists

POLEC4T avatar May 04 '24 15:05 POLEC4T

bug still exists

SorooshDeveloper avatar Jun 02 '24 12:06 SorooshDeveloper

I solved this issue using a sleepAsync function. The bottom sheet needs to be fully closed before any rendering changes happen on your screen, otherwise it pops back up (for some reason). Here's what I did;

	
bottomSheetModalRef.current?.close();
await sleepAsync(200);
// Do anything else with the page

MrgSub avatar Jun 05 '24 21:06 MrgSub

@gorhom i get this issue when i set the first modal to be enableDismissonClose={false}

SorooshDeveloper avatar Jun 06 '24 18:06 SorooshDeveloper

Facing this and similar possibly related issue in latest alpha as well.

Using dismiss() makes the dismissed sheet pop back up no matter what you do in case there is some change in state or data that triggers rendering in the sheet.

Using close() fixes the issue partially but in my case I also had to delay any data/state changes for more than 250ms (this is the time for the sheet to fully animate).

igorm-cr avatar Jun 14 '24 13:06 igorm-cr

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.

github-actions[bot] avatar Jul 15 '24 09:07 github-actions[bot]

This issue was closed because it has been stalled for 5 days with no activity.

github-actions[bot] avatar Jul 20 '24 09:07 github-actions[bot]