react-native-bottom-sheet: [v4] | Keyboard causing bottom-sheet to close down completely when using enableDynamicSizing (Android only)

Bug

I’ve recently refactored old logic behind the bottom sheet modal since I used useBottomSheetDynamicSnapPoints and changed it to enableDynamicSizing. Everything is working fine but when using with Input Field the bottom-sheet is closing only on Android larger devices.

Changing the keyboardBehavior: 'interactive' to: keyboardBehavior={Platform.OS === 'ios' ? 'interactive' : 'fillParent'} made unwanted closing to go away but now the modal doesn’t look really good since it will take up whole screen. Ideally I want to have modal just above the keyboard, something that I had before with older logic.

The linked issue but without using enableDynamicSizing is here it’s same now for me since 'interactive' doesn’t work.

Environment info

Library Version
@gorhom/bottom-sheet 4.5.1
react-native 0.72.4
react-native-reanimated 3.5.2
react-native-gesture-handler 2.12.1

Steps To Reproduce

  1. Using enableDynamicSizing
  2. Add these properties:
    keyboardBehavior='interactive'
     keyboardBlurBehavior='restore'
     android_keyboardInputMode='adjustResize'
  1. Add input field inside the Bottom Sheet modal
  2. Open the bottom sheet and try to enter something.
  3. FYI: I also tried the: BottomSheetTextInput and regular input

Describe what you expected to happen:

  1. Open the keyboard bellow the modal.
  2. Render modal above the keyboard same as it’s on IOS devices

When I added logging before closing down I get this in console:

LOG [BottomSheetContainer::handleContainerLayout] height:779.8095092773438 LOG [BottomSheetModal::handleDismiss] currentIndexRef:0 minimized:false LOG [BottomSheetModal::handleDismiss] currentIndexRef:0 minimized:false LOG [BottomSheetModal::handleDismiss] currentIndexRef:-1 minimized:false LOG [BottomSheetModal::handleDismiss] currentIndexRef:-1 minimized:false LOG [BottomSheetModal::handleDismiss] currentIndexRef:1 minimized:false

Not sure if info can be used for debugging. 🤔

Reproducible sample code

 <BottomSheetModal
      ref={bottomSheetModalRef}
      enableDynamicSizing
      onDismiss={handleDismiss}
      enablePanDownToClose={dismissOnPanDown}
      backdropComponent={renderBackdrop}
      backgroundComponent={backgroundComponent}
      keyboardBehavior='interactive'
      keyboardBlurBehavior='restore'
      android_keyboardInputMode='adjustResize'
      handleIndicatorStyle={!dismissOnPanDown && styles.handleIndicator}
      {...rest}
    >
      <BottomSheetScrollView
        contentContainerStyle={[
          styles.scrollViewContainer,
          styleScrollViewContent,
        ]}
        keyboardDismissMode='on-drag'
        bounces={bounces}
        keyboardShouldPersistTaps='handled'
        showsVerticalScrollIndicator={false}
      >
        <BottomSheetView
          style={[styles.container, styleContainer]}
        >
          {children} // <---- HERE I HAVE INPUT FIELD
        </BottomSheetView>
        <KeyboardHeight />
      </BottomSheetScrollView>
    </BottomSheetModal>

Any help would be amazing. 🙏

About this issue

  • Original URL
  • State: open
  • Created 8 months ago
  • Reactions: 6
  • Comments: 43 (3 by maintainers)

Most upvoted comments

same issue, it will close when height of bottom sheet is to low note: only on BottomSheetModal

hey @mangeshkchauhan. As I mentioned above, using the BottomSheetTextInput doesn’t fix the problem 😕

I’m also facing this issue at the moment on Android. The problem seems to stem from the fact that the content height is smaller than the keyboard height - regardless of whether you’re using snap points or dynamic height. Setting the content height higher is, of course, a straightforward solution.

Alternatively, as @mhsfh mentioned, you can fix this by setting enableDismissOnClose={false}, but it has drawbacks as mentioned above.

Here is a slightly hacky workaround that should do the trick without interfering with the dismissal:

import {
  BottomSheetModal,
  BottomSheetBackdrop,
} from "@gorhom/bottom-sheet";
// ...
//...
  const [keyboardOpen, setKeyboardOpen] = useState(false);
  const [enableDismissOnClose, setEnableDismissOnClose] = useState(true);

  useEffect(() => {
    if (Platform.OS !== 'android') return;

    const keyboardDidShowListener = Keyboard.addListener(
      'keyboardDidShow',
      () => setKeyboardOpen(true),
    );

    const keyboardDidHideListener = Keyboard.addListener(
      'keyboardDidHide',
      () => setKeyboardOpen(false),
    );

    return () => {
      keyboardDidShowListener.remove();
      keyboardDidHideListener.remove();
    };
  }, []);

  useEffect(() => {
    if (keyboardOpen) {
      setEnableDismissOnClose(false);
    }
  }, [keyboardOpen]);

  useEffect(() => {
    if (!enableDismissOnClose && keyboardOpen) {
      setTimeout(() => {
        setEnableDismissOnClose(true);
      }, 10);
    }
  }, [enableDismissOnClose, keyboardOpen]);
  
  return (
    <BottomSheetModal
      // ... other props
      index={0}
      android_keyboardInputMode="adjustResize"
      keyboardBlurBehavior="restore"
      enableDismissOnClose={enableDismissOnClose}
      backdropComponent={(props) => (
        <BottomSheetBackdrop
          {...props}
          appearsOnIndex={0}
          disappearsOnIndex={-1}
        />
      )}
      
    >
      {children}
    </BottomSheetModal>
  );
//...

This works well for dynamic height content. For snap points, it works but I’ve found the keyboard height to obscure quite a lot of the content. So I would recommend snapping to full height on input focus instead if that works for your use case.

EDIT: @ssomarii thanks for pointing out the backdropComponent, I’ve added that from my approach above

I’ve been using version 5.0.0-alpha.9 with the enableDynamicSizing parameter wrapped in a BottomSheetView, and it works flawlessly. However, in Android, the animation when the modal is raised upon opening the keyboard isn’t smooth.

Sooo, I’ve tried a solution from @apetta above and it didn’t work for me (idk why…), but with some changes it works!

I added code from useEffect with setEnableDismissOnClose(true) and timer in some pressable component for “backdropComponent” prop:

  <BottomSheetModal
    enableDismissOnClose={enableDismissOnClose}
    ref={bottomSheetRef}
    snapPoints={snapPoints}
    enableDynamicSizing={enableDynamicSizing}
    backdropComponent={() => (
      <BackdropBottomSheetModal
        onPress={() => {
          if (!enableDismissOnClose && keyboardOpen) {
            setEnableDismissOnClose(true);
          }
          bottomSheetRef.current?.dismiss();
        }}
      />
    )}>
      {children}
  </BottomSheetModal>

const BackdropBottomSheetModal: FC<IPropsBackground> = observer(
  ({onPress = () => {}}) => (
    <Pressable onPress={onPress} />
  ),
);

And in the solution I changed this useEffect:

 useEffect(() => {
  if (keyboardOpen) {
    setEnableDismissOnClose(false);
  }
}, [keyboardOpen]);

to this:

   useEffect(() => {
    setEnableDismissOnClose(!keyboardOpen);
  }, [keyboardOpen]);

Use BottomSheetTextInput instead of TextInput from react native to fix this.

In this snack, even setting explicit snap points doesn’t fix the issue:

const snapPoints = useMemo(() => [200], []);

<BottomSheetModal
  ref={bottomSheetRef}
  snapPoints={snapPoints}
>

Here to report the same/similar issue, except I’m seeing it in both Android AND iOS, and can confirm that it seems to only happen when dynamic sizing is enabled. I will note that the UI does not auto-dismiss unless I have some custom onAnimate code in place, which is necessary for us to pop the nav stack:

  const handleAnimationChange = useCallback((fromIndex: number, toIndex: number) => {
    console.log('--------from: ', fromIndex);
    console.log('--------to: ', toIndex);
    if (fromIndex !== -1 && toIndex <= 0) {
      navigation.pop();
    }
  }, []);

  <BottomSheet
      animationConfigs={hasInput ? undefined : animationConfigs}
      backdropComponent={renderBackdrop}
      backgroundStyle={styles.drawerContainer}
      contentHeight={Dimensions.get('screen').height}
      enableDynamicSizing
      enablePanDownToClose={fullyMounted}
      handleComponent={null}
      keyboardBlurBehavior="restore"
      onAnimate={handleAnimationChange}
      onChange={handleIndexChange}
      ref={bottomSheetRef}
    >
    <BottomSheetTextInput />
  </>

This seems to happen directly as a result of incorrect indices when the keyboard first appears, but only if it’s brought up AFTER the bottom sheet has finished rendering/expanding/animating. So either it’s brought up manually by tapping into a non-autofocused text input, or in the case of an autofocused input, after the keyboard is dismissed (i.e. in Android) and then brought back by tapping the input field again.

As others have mentioned, this seems to be a result of the onAnimate index unexpectedly going from 0 to -1 when the keyboard is brought up, which triggers our close handler above. It looks like this has also been previously reported in an issue that was auto-closed: https://github.com/gorhom/react-native-bottom-sheet/issues/1441

could someone provide a repo using Expo Snack: https://snack.expo.io/@gorhom/bottom-sheet-v4-reproducible-issue-template

@gorhom I tried to create a repro but was unable to even get the template snack to work: it seems to fail on a workletInit function missing, and some Googling suggested using a polyfill, which resulted in the app crashing. This was as far as I was able to diagnose, but I was not able to get that working: https://snack.expo.dev/@jwalt/bottom-sheet-v4-reproducible-issue-template

I could fix this by adding enableDismissOnClose={false} to BottomSheetModal

Otherwise, you need to put the minHeight of the content more than the keyboard height

Edit: you will have a small problem when you set enableDismissOnClose to false, the keyboard won’t be dismissed when you close the bottom sheet you can fix it by adding this callback to onChange:

import debounce from 'lodash/debounce';

const debouncedOnChange = debounce((index) => {
  if (index === -1) {
    Keyboard.dismiss();
  }
}, 10);