react-native-bottom-sheet: [v4] | [v2] Keyboard does not automatically resize on the first try. Works after that though.

Bug

The sheet does not auto resize the content height when keyboard is opened for the first time. It works after that but does not work if you clear the app and open the sheet with TextInput (BottomSheetTextInput) as a content in it for the first time. Video attached below:

Environment info

Library Version
@gorhom/bottom-sheet “^4.3.0”
react-native “^0.63.4”
react-native-reanimated “^2.4.1”
react-native-gesture-handler “~1.10.2”

Steps To Reproduce

  1. Use the below provided BottomSheet as a standalone component.
  2. Just use a Controlled TextInput as a children.
  3. Focus the input, the keyboard wont resize the content height when its visible.

Describe what you expected to happen:

  1. The content height should move up the keyboard height when its visible everytime not only after the first time

Reproducible sample code

BottomSheet.js component

import React, {useRef} from 'react';
import {BackHandler, useWindowDimensions} from 'react-native';
import {
  BottomSheetView,
  BottomSheetModal,
  BottomSheetBackdrop,
  useBottomSheetDynamicSnapPoints,
} from '@gorhom/bottom-sheet';
import {useSafeAreaInsets} from 'react-native-safe-area-context';

const BottomSheet = ({
  visible,
  setVisible = () => null,
  onDismiss = () => null,
  children = null,
  automaticallyAdjustHeight = false, // Prop 'key' with unique value as string needs to be passed to calculate the layout changes every time.
  backdropComponent = null,
  enableHandle = true,
  panToDismiss = true,
  responsive = true,
  snapPoints: _snapPoints = ['25%', '50%'],
  index = 1,
  modalStyle = {},
}) => {
  const bottomSheetRef = useRef(null);
  const {bottom: safeAreaBottom} = useSafeAreaInsets();

  const {window} = useWindowDimensions();

  const MAX_WIDTH = 600;
  const marginHorizontal =
    window.width > MAX_WIDTH ? (window.width - MAX_WIDTH) / 2 : 0;

  const snapPoints = automaticallyAdjustHeight
    ? ['CONTENT_HEIGHT']
    : _snapPoints;

  const {
    animatedHandleHeight,
    animatedSnapPoints,
    animatedContentHeight,
    handleContentLayout,
  } = useBottomSheetDynamicSnapPoints(snapPoints);

  React.useEffect(() => {
    if (visible) {
      bottomSheetRef.current?.present();
    } else {
      bottomSheetRef.current?.dismiss();
    }
  }, [visible]);

  const renderActualChildren = automaticallyAdjustHeight ? (
    <BottomSheetViewComponent
      handleContentLayout={handleContentLayout}
      key="actualChildren">
      {children}
    </BottomSheetViewComponent>
  ) : (
    children
  );

  const handleDismiss = React.useCallback(() => {
    setVisible(false);
    onDismiss?.();
  }, [onDismiss, setVisible]);

  const backPressed = React.useCallback(() => {
    setVisible(false);
    return true;
  }, [setVisible]);

  React.useEffect(() => {
    if (visible) {
      BackHandler.addEventListener('hardwareBackPress', backPressed);
    } else {
      BackHandler.removeEventListener('hardwareBackPress', backPressed);
    }
    return () => {
      BackHandler.removeEventListener('hardwareBackPress', backPressed);
    };
  }, [visible, backPressed]);

  return visible ? (
    <BottomSheetModal
      style={[
        {paddingBottom: safeAreaBottom},
        responsive ? {marginHorizontal} : {},
        modalStyle,
      ]}
      name="bottomsheet"
      ref={bottomSheetRef}
      enableHandlePanningGesture
      onDismiss={handleDismiss}
      enableContentPanningGesture={panToDismiss}
      keyboardBehavior="interactive"
      keyboardBlurBehavior="restore"
      backdropComponent={props => <Backdrop {...props} />}
      {...(automaticallyAdjustHeight ? {} : {index})}
      {...(backdropComponent !== null ? {backdropComponent} : {})}
      {...(!enableHandle ? {handleComponent: null} : {})}
      {...(automaticallyAdjustHeight
        ? {
            snapPoints: animatedSnapPoints,
            handleHeight: animatedHandleHeight,
            contentHeight: animatedContentHeight,
            key: `Modal${
              automaticallyAdjustHeight ? animatedContentHeight.value : ''
            }`,
          }
        : {snapPoints: _snapPoints})}>
      {renderActualChildren}
    </BottomSheetModal>
  ) : null;
};

function BottomSheetViewComponent({
  handleContentLayout = () => null,
  children = null,
}) {
  const {bottom: safeAreaBottom} = useSafeAreaInsets();

  return (
    <BottomSheetView
      onLayout={handleContentLayout}
      style={{
        paddingBottom: safeAreaBottom,
      }}>
      {children}
    </BottomSheetView>
  );
}

function Backdrop({...props}) {
  return (
    <BottomSheetBackdrop
      key="backdrop"
      {...props}
      pressBehavior={'close'}
      appearsOnIndex={0}
      disappearsOnIndex={-1}
    />
  );
}

export default BottomSheet;

Recording: https://user-images.githubusercontent.com/51436631/168493392-6f4a5f24-213d-49e9-828f-08aa94b0da0a.mp4

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 1
  • Comments: 19 (2 by maintainers)

Most upvoted comments

I identified the issue and preparing a fix

For now, I’m using keyboardBehavior={‘fillParent’} to avoid this bug.

So temporary solution I am using is to get the keyboard height when it opens and render a empty view with keybooard height. Its a little bit slow since it needs to render after keyboard is completely opened, but its fine until I find a solution.

So temporary solution I am using is to get the keyboard height when it opens and render a empty view with keybooard height. Its a little bit slow since it needs to render after keyboard is completely opened, but its fine until I find a solution.

@niraj-khatiwada Do you got an example of that anywhere? Maybe a snack or something you can throw together (if you’re using expo)

So this is the hook that detects the keyboard height. Should work with expo as well.

import React from 'react';
import {Keyboard} from 'react-native';

function useKeyboardHeight() {
  const [keyboardHeight, setKeyboardHeight] = React.useState(0);

  function onKeyboardDidShow(e) {
    setKeyboardHeight(e.endCoordinates.height);
  }

  function onKeyboardDidHide() {
    setKeyboardHeight(0);
  }

  React.useEffect(() => {
    Keyboard.addListener('keyboardDidShow', onKeyboardDidShow);
    Keyboard.addListener('keyboardDidHide', onKeyboardDidHide);

    return () => {
      Keyboard.removeAllListeners('keyboardDidShow');
      Keyboard.removeAllListeners('keyboardDidHide');
    };
  }, []);

  return [keyboardHeight];
}

export default useKeyboardHeight;

And inside my sheet children I just add this AdjustmentView that has height of keyboard when opened. This will automatically adjust the sheet when keyboard is opened.

/*
This version of bottom-sheet does not automatically resize when keyboard is visible for IOS. To solve this this adjustment view is added
*/
function AdjustmentView() {
  const [height] = useKeyboardHeight();

  return <View style={{height}} />;
}

Hope this helps.

EDIT: Adding new prop in v4 android_keyboardInputMode="adjustResize" fixes this problem.