Skip to content

[Android] A quick tap on a button in a scroll view also presses React Native's Pressable around it #4626

Closed

Description

@YevheniiKotyrlo

Description

On Android, a tap shorter than the tap timeout on a Touchable or RectButton inside a scroll view activates the button on the touch's UP: the scroll view defers its child's pressed state (ViewGroup.shouldDelayChildPressedState), and NativeViewGestureHandler activates on UP once the button reads pressed (NativeViewGestureHandler.kt#L159-L173). React Native's root view has handled that UP and ended its touch by then, so the cancel the activation sends reaches nothing: React Native logs Can't cancel already finished gesture. Is a child View trying to start a gesture from an UP/CANCEL event? (JSTouchDispatcher.kt#L244-L255), and a React Native Pressable around the scroll view presses as well as the button.

Held past the tap timeout, or with no scroll view between it and the Pressable, the gist's Touchable presses alone and nothing is logged.

Steps to reproduce

  1. Render the component from the gist.
  2. Tap the middle button, inside the scroll view inside the grey Pressable, with adb shell input tap <x> <y>: both [in scroll view in pressable] onPress and [pressable around scroll view] onPress are logged, and adb logcat -d | grep 'already finished gesture' shows the warning, which React Native logs under the tag unknown:ReactNative.
  3. Tap the top button, inside a scroll view alone, the same way: it presses once and the warning is logged.
  4. Tap the bottom button, inside a Pressable with no scroll view, the same way, or hold any of them for half a second: each presses alone and nothing is logged.

A link to a Gist, an Expo Snack or a link to a repository based on this template that reproduces the bug.

https://gist.github.com/YevheniiKotyrlo/0c25203fb1c38a65eaf6e6d9ce1a278e

Gesture Handler version

main at 40ac206; 3.4.0 has the same code

React Native version

0.88.0

Platforms

Android

JavaScript runtime

Hermes

Workflow

Using Expo Prebuild or an Expo development build

Build type

Release mode

Device

Android emulator

Device model

API 34 emulator (x86_64, Google APIs), Android 14

Acknowledgements

Yes

Activity

  1. m-bert commented on Oct 27, 2026

    @m-bert
    Collaborator

    Hi @YevheniiKotyrlo! Thanks for reporting this.

    Have you tried using our Pressable component instead to see if that resolves the issue? If your setup requires a different approach, please provide more information on your specific use case 😅

  2. YevheniiKotyrlo commented on Oct 27, 2026

    @YevheniiKotyrlo
    ContributorAuthor

    I have now: with gesture-handler's Pressable around the scroll view in place of React Native's, a quick tap presses only the button. That leaves a React Native responder around the scroll view, React Native's Pressable here, which cancelsJSResponder is meant to cancel: the cancel still arrives after React Native has ended the touch, as the warning on every quick tap shows. gesture-handler's Pressable as the button behaves like Touchable, and gesture-handler's ScrollView in place of React Native's doesn't change it:

    Quick tap on the button in a scroll view inside Presses Warnings
    gesture-handler's Pressable, the button a Touchable the button 1
    gesture-handler's Pressable, the button gesture-handler's Pressable the button 1
    React Native's Pressable, the button gesture-handler's Pressable both 1
    React Native's Pressable, the scroll view gesture-handler's ScrollView both 1

    No app of mine depends on this; I ran into it while testing #4614. Measured on a release build of main at f4038e9 on an API 34 emulator with adb shell input tap; taps of 50 and 80 ms press both as well, and a 200 ms one only the button. I've also fixed the logcat command in the steps and in the gist: React Native logs the warning under unknown:ReactNative, so adb logcat -s ReactNative:W didn't show it.

  3. m-bert commented on Oct 27, 2026

    @m-bert
    Collaborator

    Thanks for checking. Since our Pressable resolves this and there's no use case that requires React Native's responder around a scroll view with our buttons inside, I'm closing this. Let me know if we should reopen.

  4. YevheniiKotyrlo commented on Oct 27, 2026

    @YevheniiKotyrlo
    ContributorAuthor

    One more reading, on the 3.4.0 release, whose example app runs React Native 0.86.2: there the double press needs no scroll view. A quick tap on the gist's bottom Touchable, in the Pressable with no scroll view, presses both and logs the warning, because ReactViewGroup only stopped delaying its children's pressed state in 0.87 (ReactViewGroup.kt#L653 at v0.87.0). On React Native 0.86 and older, then, a gesture-handler button inside any view activates on UP after a quick tap, and a React Native Pressable around it presses too. Held for 400 ms, the bottom button presses alone, as on main.

  5. YevheniiKotyrlo commented on Oct 27, 2026

    @YevheniiKotyrlo
    ContributorAuthor

    I think it's worth reopening, because the scroll view turns out to be incidental: on iOS, React Native's Pressable around a gesture-handler Touchable presses together with it on every tap, quick or held, with no scroll view at all. That's the gist's bottom pair:

    <Pressable
      style={styles.pressable}
      onPress={() => console.log('[pressable around button] onPress')}>
      <Touchable
        style={styles.button}
        onPress={() => console.log('[in pressable] onPress')}
      />
    </Pressable>

    On main at f4038e9, in a Release build on an iPhone 12 Pro Max with iOS 27.0.1, taps held for 0, 80, 250 and 400 ms each log both. On iOS a button never cancels React Native's touch: RNNativeViewHandler binds the button's UIControl through its control events and attaches no recognizer (RNNativeViewHandler.mm#L227-L273), while the responder is only cancelled from RNRootViewGestureRecognizer's canBePreventedByGestureRecognizer:, which takes a gesture-handler recognizer preventing the root one (RNRootViewGestureRecognizer.m#L41-L53). So cancelsJSResponder, which buttons take from the native gesture and which is on by default, has no effect on a button on iOS. On Android the same pair presses both on a quick tap with React Native 0.85 and 0.86, which 3.4 supports, as in my last comment.

    Swapping in gesture-handler's Pressable still avoids it, so it comes down to whether cancelsJSResponder is meant to hold for buttons. If you'd rather track the iOS side as its own issue, I'll open one.

  6. m-bert commented on Oct 28, 2026

    @m-bert
    Collaborator

    Thank you for the details. We are aware that the delay differs in RN 0.87. As for the second message, your repro still uses RN Pressable and you explicitly say that our Pressable resolves this problem, therefore we will keep this issue closed.

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions