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

[Bug]: Bottom sheet does not go down after closing the keyboard if you use BottomSheetScrollView

Open exzos28 opened this issue 3 months ago β€’ 11 comments

Version

v5

Reanimated Version

v3

Gesture Handler Version

v2

Platforms

iOS

What happened?

The issue still exists, everything is the same.

Problem: When using BottomSheetScrollView, if you open and close the keyboard, BottomSheetScrollView starts to occupy all the space where the keyboard was and does not return to its original position. This only happens if the scroll content overflows when the keyboard is open (see the two videos below that demonstrate this).

Reproduction steps

https://github.com/user-attachments/assets/35e00184-67ca-4e99-b363-bcfca67281aa

https://github.com/user-attachments/assets/ec6e0720-eaaf-49f0-97ee-29661cfe2267

Reproduction sample

https://snack.expo.dev/@exzos28/f615a2?platform=ios

exzos28 avatar Oct 01 '25 12:10 exzos28

I have the exact same error, I will continue to be attentive if you find a solution.

ChaparroAlberto avatar Oct 02 '25 15:10 ChaparroAlberto

@ChaparroAlberto I temporarily fixed this like this:

    const isKeyboardVisible = useKeyboardState(state => state.isVisible); // or Keyboard.addListener('keyboardDidHide')
    useEffect(() => {
      if (Platform.OS !== β€œios”) {
        return;
      }
      if (!isKeyboardVisible) {
        bottomSheetRef.current?.snapToIndex(0);
      }
    }, [bottomSheetRef, isKeyboardVisible]);

The call happens with some delay, but it's better than nothing.

exzos28 avatar Oct 02 '25 15:10 exzos28

Okay, thanks a lot. Do you put that code in the component where you use it? Or where do you put that code snippet?

ChaparroAlberto avatar Oct 02 '25 19:10 ChaparroAlberto

in the place where you have access to the modal ref

exzos28 avatar Oct 02 '25 20:10 exzos28

@ChaparroAlberto I temporarily fixed this like this:

    const isKeyboardVisible = useKeyboardState(state => state.isVisible); // or Keyboard.addListener('keyboardDidHide')
    useEffect(() => {
      if (Platform.OS !== β€œios”) {
        return;
      }
      if (!isKeyboardVisible) {
        bottomSheetRef.current?.snapToIndex(0);
      }
    }, [bottomSheetRef, isKeyboardVisible]);

The call happens with some delay, but it's better than nothing.

thank you so much, i solve this issue with keyboard listener useEffect(() => { Keyboard.addListener("keyboardDidHide", () => { bottomSheetRef.current?.snapToIndex(0); }); }, []);

MuhammadAnsoriNasution avatar Oct 03 '25 03:10 MuhammadAnsoriNasution

Here is my workaround for the keyboard avoiding bugs. This is a replacement for BottomSheetTextInput.

The code is the same as the original except I manually set the keyboard state status: KEYBOARD_STATUS.SHOWN / status: KEYBOARD_STATUS.HIDDEN, this fixes the keyboard avoiding getting out of sync. I also added a waitUntil to fix a race condition where focusing the text input doesn't work while the bottom sheet is animating.

import { ANIMATION_STATUS, KEYBOARD_STATUS, useBottomSheetInternal } from '@gorhom/bottom-sheet';
import { forwardRef, memo, useCallback, useEffect, useImperativeHandle, useRef } from 'react';
import type { NativeSyntheticEvent, TextInputFocusEventData, TextInputProps } from 'react-native';
import { TextInput as RNTextInput, findNodeHandle } from 'react-native';
import { TextInput } from 'react-native-gesture-handler';
import { waitUntil } from '../utils/MiscHelpers';

export type BottomSheetTextInputProps = TextInputProps;

const BottomSheetTextInputComponent = forwardRef<TextInput | undefined, BottomSheetTextInputProps>(
  ({ onFocus, onBlur, ...rest }, providedRef) => {
    //#region refs
    const ref = useRef<TextInput>(null);
    //#endregion

    //#region hooks
    const { animatedKeyboardState, textInputNodesRef, animatedAnimationState } =
      useBottomSheetInternal();
    //#endregion

    //#region callbacks
    const handleOnFocus = useCallback(
      async (args: NativeSyntheticEvent<TextInputFocusEventData>) => {
        const targetValue = args.nativeEvent.target;

        if (onFocus) {
          onFocus(args);
        }

        // Wait until the animation is not running to avoid race condition
        await waitUntil(() => animatedAnimationState.get().status !== ANIMATION_STATUS.RUNNING, 10);

        animatedKeyboardState.set((state) => ({
          ...state,
          target: targetValue,
          status: KEYBOARD_STATUS.SHOWN,
        }));
      },
      [onFocus, animatedKeyboardState, animatedAnimationState]
    );
    const handleOnBlur = useCallback(
      (args: NativeSyntheticEvent<TextInputFocusEventData>) => {
        const keyboardState = animatedKeyboardState.get();
        const currentFocusedInput = findNodeHandle(
          RNTextInput.State.currentlyFocusedInput() as any
        );

        /**
         * we need to make sure that we only remove the target
         * if the target belong to the current component and
         * if the currently focused input is not in the targets set.
         */
        const shouldRemoveCurrentTarget = keyboardState.target === args.nativeEvent.target;
        const shouldIgnoreBlurEvent =
          currentFocusedInput && textInputNodesRef.current.has(currentFocusedInput);

        if (shouldRemoveCurrentTarget && !shouldIgnoreBlurEvent) {
          animatedKeyboardState.set((state) => ({
            ...state,
            target: undefined,
            status: KEYBOARD_STATUS.HIDDEN,
          }));
        }

        if (onBlur) {
          onBlur(args);
        }
      },
      [onBlur, animatedKeyboardState, textInputNodesRef]
    );
    //#endregion

    //#region effects
    useEffect(() => {
      const componentNode = findNodeHandle(ref.current);

      if (!componentNode) {
        return;
      }

      if (!textInputNodesRef.current.has(componentNode)) {
        textInputNodesRef.current.add(componentNode);
      }

      return () => {
        const componentNode = findNodeHandle(ref.current);

        if (!componentNode) {
          return;
        }

        const keyboardState = animatedKeyboardState.get();
        /**
         * remove the keyboard state target if it belong
         * to the current component.
         */

        if (keyboardState.target === componentNode) {
          const currentState = animatedKeyboardState.get();

          animatedKeyboardState.set({
            ...currentState,
            target: undefined,
          });
        }

        if (textInputNodesRef.current.has(componentNode)) {
          textInputNodesRef.current.delete(componentNode);
        }
      };
    }, [textInputNodesRef, animatedKeyboardState]);
    useImperativeHandle(providedRef, () => ref.current ?? undefined, []);
    //#endregion

    return <TextInput ref={ref} onFocus={handleOnFocus} onBlur={handleOnBlur} {...rest} />;
  }
);

export const BottomSheetTextInput = memo(BottomSheetTextInputComponent);

BottomSheetTextInput.displayName = 'BottomSheetTextInput';

robgraeber avatar Oct 19 '25 05:10 robgraeber

+1, same issue

Insanefoam avatar Nov 04 '25 05:11 Insanefoam

+1, similar issue but using BottomSheetModal with BottomSheetView.

I created a workaround that is working smoothly.

import { BottomSheetModal } from "@gorhom/bottom-sheet";
import { useEffect, useRef } from "react";
import { Keyboard } from "react-native";

const MyCustomSheetModal = () => {
  //#region refs
  const sheetRef = useRef<BottomSheetModal>(null);
  const lastIndexRef = useRef(-1);  

  //#region effects
  useEffect(() => {
    const listener = Keyboard.addListener("keyboardDidHide", () => {
      sheetRef.current?.snapToIndex(lastIndexRef.current);
    });

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

  //#region handlers
  function handleChange(index: number) {
    if (!Keyboard.isVisible()) {
      lastIndexRef.current = index;
    }
  }

  return <BottomSheetModal ref={sheetRef} onChange={handleChange} />
}

predofrazao avatar Nov 04 '25 18:11 predofrazao

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 Dec 05 '25 09:12 github-actions[bot]

Same issue

Meycon avatar Dec 05 '25 14:12 Meycon

+1

Vayts avatar Dec 09 '25 21:12 Vayts

+1

faizan2301 avatar Dec 17 '25 12:12 faizan2301