gorhom / react-native-bottom-sheet

A performant interactive bottom sheet with fully configurable options 🚀
https://gorhom.dev/react-native-bottom-sheet/
MIT License
7.13k stars 779 forks source link

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

Closed devoren closed 4 months ago

devoren commented 1 year ago

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

2) 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>
</>
enchorb commented 1 year ago

Same issue here, this was fine in v4

devoren commented 1 year ago

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

Simoon-F commented 1 year ago

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();
devoren commented 1 year ago

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

github-actions[bot] commented 1 year ago

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.

devoren commented 1 year ago

the bug still exists

Bad-Listener commented 1 year ago

yeap, I confirm, still exists!

michaelbrant commented 11 months ago

anyone find a work around?

devoren commented 11 months ago

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

github-actions[bot] commented 10 months ago

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.

devoren commented 10 months ago

the bug still exists

cristiano-linvix commented 10 months ago

any suggestion to solve this problem?

michaelbrant commented 10 months ago

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>
  );
};
flodlc commented 9 months ago

Same here

cristiano-linvix commented 9 months ago

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

github-actions[bot] commented 8 months ago

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.

devoren commented 8 months ago

bug still exists

POLEC4T commented 8 months ago

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.

michaelbrant commented 8 months ago

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";
POLEC4T commented 8 months ago

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

POLEC4T commented 8 months ago

H

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

michaelbrant commented 8 months ago

    <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
github-actions[bot] commented 7 months ago

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.

POLEC4T commented 7 months ago

bug still exists

SorooshDeveloper commented 6 months ago

bug still exists

MrgSub commented 6 months ago

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
SorooshDeveloper commented 5 months ago

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

igorm-cr commented 5 months ago

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

github-actions[bot] commented 4 months ago

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] commented 4 months ago

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

R0LLeX commented 1 month ago

bug still exists

BR1G00 commented 3 weeks ago

You can fix this using this props on the modalstackBehavior="replace"

kipertech commented 19 hours ago

Maybe not perfect but this is what I'm doing to work around both the "Reduce Motion" issue and this issue. When "Reduce Motion" is off then I just don't use this animation override at all.

(I am on 4.4.7 of this library, which doesn't have the ReduceMotion.Never implemented always).

// Reduce Motion issue: https://github.com/gorhom/react-native-bottom-sheet/pull/1743
const animationConfigs = useBottomSheetSpringConfigs({
    reduceMotion: ReduceMotion.Never,
    // Make it super fast so that it doesn't get blocked by subsequent animations / setStates
    mass: 0.1,
    duration: 10,
});