React-native: TouchableOpacity inside ScrollView trigger onPress while scrolling - Android only

Created on 27 Nov 2019  路  32Comments  路  Source: facebook/react-native

I recently updated react native from 59.10 to 60.6.
I have a View with FlatList each item in list is TouchableOpacity.
Until now I was able to scroll list and when stop press item and trigger onPress event.
Now while I am scrolling onPress is triggered randomly. It somehow trigger onPress during myScroll.
And this is happening only on android devices (But I am testning on v8)

React Native version:
System:
OS: macOS Mojave 10.14.6
CPU: (4) x64 Intel(R) Core(TM) i5-4258U CPU @ 2.40GHz
Memory: 18.89 MB / 8.00 GB
Shell: 3.2.57 - /bin/bash
Binaries:
Node: 10.17.0 - /usr/local/opt/node@10/bin/node
Yarn: 1.19.1 - /usr/local/bin/yarn
npm: 6.11.3 - /usr/local/opt/node@10/bin/npm
Watchman: 4.9.0 - /usr/local/bin/watchman
SDKs:
iOS SDK:
Platforms: iOS 13.1, DriverKit 19.0, macOS 10.15, tvOS 13.0, watchOS 6.0
Android SDK:
API Levels: 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28
Build Tools: 19.1.0, 20.0.0, 21.1.2, 22.0.1, 23.0.1, 23.0.2, 23.0.3, 24.0.0, 24.0.1, 24.0.2, 24.0.3, 25.0.0, 25.0.2, 25.0.3, 26.0.0, 26.0.1, 26.0.2, 26.0.3, 27.0.3, 28.0.0, 28.0.2, 28.0.3
System Images: android-25 | Google APIs Intel x86 Atom, android-26 | Google APIs Intel x86 Atom, android-27 | Google APIs Intel x86 Atom
IDEs:
Android Studio: 3.5 AI-191.8026.42.35.5900203
Xcode: 11.1/11A1027 - /usr/bin/xcodebuild
npmPackages:
react: 16.8.6 => 16.8.6
react-native: 0.60.6 => 0.60.6
npmGlobalPackages:
create-react-native-app: 0.0.6
generator-react-native-ignite: 1.13.0
react-native-cli: 2.0.1
react-native-git-upgrade: 0.2.7
react-native-ignite: 1.13.0

Steps To Reproduce

It's just FlatList with TouchableOpacities inside.

Describe what you expected to happen:
Not catch onPress event while scroll only if press on item in a list.
This was working fine on 59.10

Bug ScrollView TouchableOpacity Verify on Latest Version Android

Most helpful comment

I just imported TouchableOpacity from import 'react-native-gesture-handler' and this one work fine.
I have very large project so I would like not to change this everywhere.
Any reason why react-native TouchableOpacity has issue and this one doesn't?

All 32 comments

It looks like you are using an older version of React Native. Please update to the latest release, v0.61 and verify if the issue still exists.

The "Resolution: Old Version" label will be removed automatically once you edit your original post with the results of running react-native info on a project using the latest release.

could you post your code here?

@cinder92 here you go. I didn't put all code from Touchable as it's a lot of texts inside.
As you can see nothing special in code but on Android make real problem. As event is fired during scrolls.

<FlatList
            ref={ref => (this.myList = ref)}
            getItemLayout={(item, index) => ({
              length: 230,
              offset: 230 * index,
              index
            })}
            extraData={this.state}
            showsHorizontalScrollIndicator={false}
            horizontal={true}
            data={[1,2,3,4,5,6,7,8,9,10]}
            renderItem={({ item }) => {
              return (
                  <TouchableOpacity style={{width: 230, height: 230, backgroundColor: '#f00', margin:5}} 
                  onPress={() => 
                       Alert.alert('test')}>
                <Text>{"box"}</Text>         
              </TouchableOpacity>
              )}}
          />

Here you can see video of this:
Nov-27-2019 21-09-02

I just imported TouchableOpacity from import 'react-native-gesture-handler' and this one work fine.
I have very large project so I would like not to change this everywhere.
Any reason why react-native TouchableOpacity has issue and this one doesn't?

Having the same problem on v0.61.4

We noticed it too on a very fresh project setup with 0.61.2.

Same here 0.61.2, was working in 0.58 or below. Happens on Android only.

The TouchableOpacity from react-native-gesture-handler does work but sometimes it ruins the layout of its children. Now it's affecting all the packages using touchable components such as 'react-native-snap-carousel' (Touchable triggered while snapping to the next slide)

Any method to fix this?

After a few days, still haven't found a good solution, seems that the scroll responder of ScrollView/FlatList doesn't properly capture the touch responder (?) so the touchable continues to be the touch responder.

However, instead of using TouchableOpacity from react-native-gesture-handler which ruined the layout of my components, I came up with a very TEMPORARY 'solution', which is to modify the React Native source Touchable.js (node_modules/react-native/Libraries/Components/Touchable/Touchable.js)

The mechanism is to add lines for disabling the original move responder of touchable.

Line 501 (_as at RN 0.61.5_):

/**
   * Place as callback for a DOM element's `onResponderMove` event.
   */
touchableHandleResponderMove: function(e: PressEvent) {

    // ===== Add the following lines ===== 
    if(Platform.OS=='android' && this.props.removeMoveResponder) {         //android's issue only, so disable the touch move for android
      this._cancelLongPressDelayTimeout();
      this._receiveSignal(Signals.LEAVE_PRESS_RECT, e);
      return;
    }
    // ===== Add the lines above =====


    // Measurement may not have returned yet.
    if (!this.state.touchable.positionOnActivate) {
      return;
    }

....
....

Whenever you place a touchable inside the ScrollView, add a removeMoveResponder prop to it.

<TouchableOpacity onPress={()=>{}}  removeMoveResponder>
    <Text>I do not respond to touch move / scroll</Text>
</TouchableOpacity>

Still finding an elegant solution to it, such as detecting whether the parent of the touchable is a ScrollView / FlatList. It will be much better if there's an official fix, like fixing the touch capturing issue (which should be the way to go but I have no idea how to).

Alright, I found the 'solution' above too buggy in real devices. The onPress action can't be fired as the move gesture is too sensitive to be triggered, so I added a touch move allowance under the 'long press allowance' code.

Accomplished by modifying the React Native source Touchable.js (node_modules/react-native/Libraries/Components/Touchable/Touchable.js)

Line 547 (or sth close):
Once the finger moves out, add a scroll flag to make sure the touch won't be fired again when the finger comes back to the original position

if (this.pressInLocation) {
      const movedDistance = this._getDistanceBetweenPoints(
        pageX,
        pageY,
        this.pressInLocation.pageX,
        this.pressInLocation.pageY,
      );
      if (movedDistance > LONG_PRESS_ALLOWED_MOVEMENT) {
        this._cancelLongPressDelayTimeout();
       //======== Add the following line =========
        if(Platform.OS=='android' && this.props.removeMoveResponder) this.scrolledFlag = true;
      }
    }

Line 505 (or sth close, the touchableHandleResponderMove function):
If it's determined as scrolled state, then just send a 'moved out of rect' signal.

touchableHandleResponderMove: function(e: PressEvent) {

    //=====Add or change to the following lines=====
    if(this.scrolledFlag) {
      this._cancelLongPressDelayTimeout();
      this._receiveSignal(Signals.LEAVE_PRESS_RECT, e);
      return;
    }
    //=====Add or change to the lines above=====


    // Measurement may not have returned yet.
    if (!this.state.touchable.positionOnActivate) {
      return;
    }

....
.....

Line 488 (touchableHandleResponderRelease function):

touchableHandleResponderRelease: function(e: PressEvent) {
    this.pressInLocation = null;
    this._receiveSignal(Signals.RESPONDER_RELEASE, e);
    this.scrolledFlag = false;             // disable the scroll flag, everything reset
  },

And still, waiting for an official fix (or some fixes suggested by the pros).

I'm having the same issue in both Android and IOS using a FlatList with TouchableOpacity and react-native 0.61.5. I also have "react-native-gesture-handler": "^1.5.2" installed in this project, but I'm importing TouchableOpacity from react-native, not from gesture handler.

This solution from stackoverflow seems to be working for me: https://stackoverflow.com/a/37642488/9147743

Here's what I've done in my own code:

<TouchableOpacity delayPressIn={5} delayPressOut={5} delayLongPress={5}

This seems to be a documentation issue. Right now delayPressIn, delayPressOut, and delayLongPress are only listed on the documentation for TouchableWithOutFeedback, and not in the documentation for TouchableOpacity, even though they work for both.

I had the same issue in Android only with React Native 0.61.4. I would not like to change the souce code of React Native. I did the trick by creating a native UI module that generates AndroidButtons for React Native.

public class AndroidButtonManager extends SimpleViewManager<Button> {


    @NonNull @Override public String getName() {
        return "AndroidButton";
    }

    @NonNull @Override
    protected Button createViewInstance(@NonNull final ThemedReactContext reactContext) {
        Activity currentActivity = reactContext.getCurrentActivity();
        Button button = new Button(currentActivity);
        button.setOnClickListener(new View.OnClickListener() {
            @Override public void onClick(View view) {
                WritableMap event = Arguments.createMap();
                reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(view.getId(), "onPress", event);
            }
        });
        return button;
    }

    @Nullable @Override public Map getExportedCustomBubblingEventTypeConstants() {
        return MapBuilder.builder()
                .put( "onPress",  MapBuilder.of( "phasedRegistrationNames",  MapBuilder.of("bubbled", "onPress")))
                .build();
    }
}

Creating AndroidButton in react native:

import * as React from 'react'
import {requireNativeComponent, ViewStyle} from 'react-native'

const RNAndroidButton = requireNativeComponent('AndroidButton')

export interface AndroidButtonProps {
  style?: ViewStyle
  onPress?: () => void
}

class AndroidButton extends React.Component<AndroidButtonProps> {
  constructor(props: AndroidButtonProps) {
    super(props)
    this.state = {}
  }

  public render() {
    const {onPress, style} = this.props
    return (
      <RNAndroidButton
        onPress={onPress}
        style={{width: '100%', height: '100%', position: 'absolute', ...style}}
      />
    )
  }
}

export default AndroidButton

Using AndroidButton in react-native:

 public render() {
    return (
      <View>
       <AndroidButton onPress={() => console.log('did press me')} />
       ...
      </View>
    )
}

Same problem here. I'm in "react-native": "0.62.2".

I have an FlatList with TouchableOpacity, and the expected behavior is: when I scrolling FlatList, TouchableOpacity onPress don't should be called. His be called only if I press him, not when scrolling his father.

Android

  • Device: Motorola XT1672 (Moto g5)
  • OS Version: 8.1.0

android

IOS

  • Emulator: Iphone 11
  • OS Version: 13.3

ios

CODE

Just FlatList with an TouchableOpacity as renderItem.

Partial solution:

I have added delayPressIn={100} to my TouchableOpacity. Isn't the better but resolve in parts. Sometimes is possible activate opacity effect with scroll yet, but not always as before.
The problem is when I click very fast in TouchableOpacity, the opacity effect don't active before navigation, in my case.

I have the same problem, too.

I wrote a pull https://github.com/facebook/react-native/pull/28982 to fix a similar issue.

I tested as I'm willing to fix this issue, but I can not reproduce the problem on emulator and real device.

React Native Master branch.

Try this code if you wan to put a TouchableOpacity in a ScrollView / FlatList

import {TouchableWithoutFeedback} from 'react-native-gesture-handler'

class TouchableOpacityEx extends Component {
    constructor (props) {
        super (props);
        this.state = {};

        this.state.opactiy = 1;
    }

    render () {
        return  <TouchableWithoutFeedback
                    onPressIn={() => {
                        // touch began
                        this.setState ({opacity : this.props.activeOpacity || 0.7}) 
                    }}

                    onPressOut={() => {
                        // touch end 
                        this.setState ({opacity : 1}) 
                    }}

                    {...this.props}
                >
                    <View style={[this.props.style,{opacity : this.state.opacity}]}>
                        {this.props.children}
                    </View>
                </TouchableWithoutFeedback>
    }
}

then Just Change TouchableOpacity to TouchableOpacityEx

It works fine for me

Same problem here. I'm in "react-native": "0.62.2".

I have an FlatList with TouchableOpacity, and the expected behavior is: when I scrolling FlatList, TouchableOpacity onPress don't should be called. His be called only if I press him, not when scrolling his father.

Android

  • Device: Motorola XT1672 (Moto g5)
  • OS Version: 8.1.0

android

IOS

  • Emulator: Iphone 11
  • OS Version: 13.3

ios

CODE

Just FlatList with an TouchableOpacity as renderItem.

Partial solution:

I have added delayPressIn={100} to my TouchableOpacity. Isn't the better but resolve in parts. Sometimes is possible activate opacity effect with scroll yet, but not always as before.
The problem is when I click very fast in TouchableOpacity, the opacity effect don't active before navigation, in my case.

try this code :

import {TouchableWithoutFeedback} from 'react-native-gesture-handler'

class TouchableOpacityEx extends Component {
    constructor (props) {
        super (props);
        this.state = {};

        this.state.opactiy = 1;
    }

    render () {
        return  <TouchableWithoutFeedback
                    onPressIn={() => {
                        // touch began
                        this.setState ({opacity : this.props.activeOpacity || 0.7}) 
                    }}

                    onPressOut={() => {
                        // touch end 
                        this.setState ({opacity : 1}) 
                    }}

                    {...this.props}
                >
                    <View style={[this.props.style,{opacity : this.state.opacity}]}>
                        {this.props.children}
                    </View>
                </TouchableWithoutFeedback>
    }
}

I have the same problem, too.

Try this code

import {TouchableWithoutFeedback} from 'react-native-gesture-handler'

class TouchableOpacityEx extends Component {
    constructor (props) {
        super (props);
        this.state = {};

        this.state.opactiy = 1;
    }

    render () {
        return  <TouchableWithoutFeedback
                    onPressIn={() => {
                        // touch began
                        this.setState ({opacity : this.props.activeOpacity || 0.7}) 
                    }}

                    onPressOut={() => {
                        // touch end 
                        this.setState ({opacity : 1}) 
                    }}

                    {...this.props}
                >
                    <View style={[this.props.style,{opacity : this.state.opacity}]}>
                        {this.props.children}
                    </View>
                </TouchableWithoutFeedback>
    }
}

I'm having the same issue in both Android and IOS using a FlatList with TouchableOpacity and react-native 0.61.5. I also have "react-native-gesture-handler": "^1.5.2" installed in this project, but I'm importing TouchableOpacity from react-native, not from gesture handler.

This solution from stackoverflow seems to be working for me: https://stackoverflow.com/a/37642488/9147743

Here's what I've done in my own code:

<TouchableOpacity delayPressIn={5} delayPressOut={5} delayLongPress={5}

This seems to be a documentation issue. Right now delayPressIn, delayPressOut, and delayLongPress are only listed on the documentation for TouchableWithOutFeedback, and not in the documentation for TouchableOpacity, even though they work for both.

import {TouchableWithoutFeedback} from 'react-native-gesture-handler'

class TouchableOpacityEx extends Component {
    constructor (props) {
        super (props);
        this.state = {};

        this.state.opactiy = 1;
    }

    render () {
        return  <TouchableWithoutFeedback
                    onPressIn={() => {
                        // touch began
                        this.setState ({opacity : this.props.activeOpacity || 0.7}) 
                    }}

                    onPressOut={() => {
                        // touch end 
                        this.setState ({opacity : 1}) 
                    }}

                    {...this.props}
                >
                    <View style={[this.props.style,{opacity : this.state.opacity}]}>
                        {this.props.children}
                    </View>
                </TouchableWithoutFeedback>
    }
}

Any further updates on this? Just updated my app to 0.63.2 and the TouchableOpacity inside a ScrollView are super sensitive. Was working fine on 0.61.2. I've tested with delayPressIn but it just doesn't feel right.

so,how to fix this problem? I try all methods, not work for me

It worked for me using property onPress instead of onPressIn for both TouchableOpacity and TouchableWithoutFeedback

@dcolonv

It worked for me using property onPress instead of onPressIn for both TouchableOpacity and TouchableWithoutFeedback

onPress has always worked - it's just extremely sensitive which is why I've tried the delayPressIn & onPressIn

Pressable was made to support better press events. Let me know if that helps out.

https://reactnative.dev/docs/pressable

I found the solution here
Hope it helps!

I import both TouchableOpacity and ScrollView from 'react-native-gesture-handler', and it works fine!
You might see the opacity changes but when I tried to console.log the handler, it doesn't count as 'pressed'

@danmaas just looking at the patches you've applied - I don't have this file in Libraries/Pressability/__tests__/Pressability-test.js

@carissacks this solution worked for me.

The __tests__ patch is part of react-native's internal test suite, you don't need it.

same problem here
with RN 0.63.3 and Pressable

same here

This problem also applies to iOS. Tested version with Flatlist and version 0.63.3.

Fortunately, _react-native-gesture-handler_ fixes the problem.

Scrolling the Facebook main app news feed, on Android, suffers from this issue. Tapping it to drag is not an item select press gesture. Actual behavior creates a constant flicker effect when browsing the feed. Makes one afraid of touching it in order not to trigger it again. Twitter (ReactiveX) doesn't have this issue.

Was this page helpful?
0 / 5 - 0 ratings