Skip to content

[Bug]: Keyboard show is ignored when the new keyboard is 1pt taller than the one that just closed (status + height collision) #2798

Open

Description

@BuiHung1612

Version

v5 (5.2.14)

Reanimated Version

v4 (4.7.1, react-native-worklets 0.13.1; the Snack uses Expo SDK 55 with its bundled 4.2.1 / worklets 0.7.4 / gesture handler 2.30)

Gesture Handler Version

v3 (3.5.0)

Platforms

iOS (React Native 0.87, New Architecture)

What happened?

A keyboard that opens right after another one closed is sometimes ignored. The sheet stays where it was with the keyboard hidden, so its content ends up under the keyboard.

The reaction to keyboard changes in BottomSheet.tsx keys on the sum of status and height:

useAnimatedReaction(
  () =>
    animatedKeyboardState.get().status + animatedKeyboardState.get().height,
  (result, _previousResult) => {
    if (result === _previousResult) {
      return;
    }
    ...

KEYBOARD_STATUS is SHOWN = 1 and HIDDEN = 2, and on hide the state keeps the previous height. So if the keyboard closes at height h and the next one opens at h + 1:

  • after hide: HIDDEN (2) + h
  • after show: SHOWN (1) + (h + 1), which is the same value

The reaction returns early, evaluatePosition(ANIMATION_SOURCE.KEYBOARD) never runs, and the sheet keeps its no-keyboard position even though animatedKeyboardState now says SHOWN with the new height.

We hit it in a dynamic-height BottomSheetModal (keyboardBehavior="interactive") with two steps:

  1. An email field (default keyboard, autocorrect off). On iPhone 18 Pro / iOS 27 the keyboard is 301pt.
  2. On submit, the email step is replaced by a one-time-code step whose input uses a number pad, which is 302pt.

Logging animatedKeyboardState and animatedPosition every 150ms after the second step mounts:

 652ms kb=2 target=undefined h=301 pos=495
 985ms kb=1 target=2728      h=302 pos=495   <- keyboard shown, position unchanged
1135ms kb=1 target=2728      h=302 pos=495

With a 379pt sheet on an 874pt screen, the expected position is 874 − 302 − 379 = 193. It stays at 495, the no-keyboard position. Whether it happens depends on the two keyboard heights differing by exactly 1pt, so it looks intermittent across devices and keyboard configurations (suggestion bar on or off).

Reproduction steps

  1. Present a BottomSheetModal with enableDynamicSizing and keyboardBehavior="interactive".
  2. Inside it, focus a BottomSheetTextInput whose keyboard is height h (for example an email field with autoCorrect={false}).
  3. Blur it (Keyboard.dismiss()), then focus a second BottomSheetTextInput whose keyboard is h + 1 (for example inputMode="numeric" / number pad).
  4. The sheet does not move up; the second input is under the keyboard.

To confirm the cause without matching keyboard heights, set animatedKeyboardState from { status: HIDDEN, height: 301 } to { status: SHOWN, height: 302 }: the keyboard reaction does not fire.

Reproduction sample

https://snack.expo.dev/HRWIdMn5azuFzwO7y6fDL

Real keyboard heights differ per device, so the Snack sets the sheet's animatedKeyboardState directly (via useBottomSheetInternal) and shows the sheet position:

  • Collide: hide at 301, then show at 302. 2 + 301 === 1 + 302, the reaction is skipped and the sheet does not move.
  • Control: hide at 301, then show at 300. The sum differs, the reaction runs and the sheet lifts by about 300.

On a real device the same happens when a keyboard opens 1pt taller than the one that just closed (in our case an email keyboard at 301pt followed by a number pad at 302pt on iPhone 18 Pro / iOS 27).

(Re-filed with the Snack link; the first attempt, #2797, was auto-closed because it had none.)

Suggested fix

Key the reaction on a value that can't collide, e.g. a string:

--- a/src/components/bottomSheet/BottomSheet.tsx
+++ b/src/components/bottomSheet/BottomSheet.tsx
@@ useAnimatedReaction(
-      () =>
-        animatedKeyboardState.get().status + animatedKeyboardState.get().height,
+      () =>
+        `${animatedKeyboardState.get().status}:${animatedKeyboardState.get().height}`,
       (result, _previousResult) => {

(Same change in lib/module and lib/commonjs.) We apply this with yarn patch and the sheet now lifts above the keyboard every time in the scenario above (previously it failed about every other attempt).

Relevant log output

See the animatedKeyboardState / animatedPosition log above.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions