Random onPress handle in FlatList items while swiping pages
Environment
expo v42.0.0
"react-native-pager-view": "5.4.0"
Description
Random onPress handle in FlatList items while swiping pages Especially noticeable on low-end android devices and in apps with many heavy components
Reproducible Demo
There is a function in demo that imitate CPU intensive operation in order for the issue to be stably reproduced in simple demo on high-end devices
https://snack.expo.dev/@sane.ecg/pager-onpress-issue
https://user-images.githubusercontent.com/764967/128613740-e384306f-a100-4b7c-9ade-7db12683d6e5.mp4
Face the same issue with ios + hermes on.
For more information
In the past, i'v use "@react-native-community/viewpager": "5.0.11" and it work perfectly on android.
But currently when try ios build, this problem occured.
I'v also update to "react-native-pager-view": "5.4.0" but this still persist.
The only solution that work for me now is listen for onPageScrollStateChanged event, store the scroll state and only trigger onPress on other event if scrollState is idle
Depending on the type of touchable component you're using, you should be able to use the react-native-gesture-handler version to get the desired behavior. These components are more tightly integrated with the native gesture system(s), and while I'm not entirely sure what changes were made to react-native-pager-view from the old repo, it seems like it uses the react-native gesture responder system less.
P.S. While the react-native-gesture-handler docs say that TouchableWithoutFeedback and the like are drop-in replacements for the regular React-Native components, there are a few annoying differences regarding layout like this.
I have this problem on Android, and ignoring onPress if scrollState !== 'idle' helps (thank you, @kyoz). Unfortunately, onPageScrollStateChanged does not fire 100% of the time. I have two nested PagerView components, and the the inner one's onPageScrollStateChanged is unreliable when the outer one has recently scrolled. If I ignore onPress until the outer PagerView has been idle for a period of time, the problem is less likely to occur.
I usereact-native-tab-view v3 is depend on this, same issue!
I hope solve this problem.
I guess it's because of the async between the JavaScript thread and Main Thread.
If you use v2 (use react-native-reanimateed builded), It won't happen.
Any update?
@grabbou Could we get an update on this please?
Found a solution!
Replace <TouchableHighlight with <FixedTouchableHighlight in your FlatList
// FixedTouchableHighlight.js
import React, { useRef } from 'react';
import { TouchableHighlight } from 'react-native';
export default function FixedTouchableHighlight({
onPress,
onPressIn,
...props
}) {
const _touchActivatePositionRef = useRef(null);
function _onPressIn(e) {
const { pageX, pageY } = e.nativeEvent;
_touchActivatePositionRef.current = {
pageX,
pageY,
};
onPressIn?.(e);
}
function _onPress(e) {
const { pageX, pageY } = e.nativeEvent;
const absX = Math.abs(_touchActivatePositionRef.current.pageX - pageX);
const absY = Math.abs(_touchActivatePositionRef.current.pageY - pageY);
const dragged = absX > 2 || absY > 2;
if (!dragged) {
onPress?.(e);
}
}
return (
<TouchableHighlight onPressIn={_onPressIn} onPress={_onPress} {...props}>
{props.children}
</TouchableHighlight>
);
}
@Gregoirevda Thank you for the workaround. Hopefully this is a temporary fix and a future update will prevent touchable from activating during screen transitions.
I have the same problem on React Navigation v6
Still an issue +1
Found a solution! Replace
<TouchableHighlightwith<FixedTouchableHighlightin yourFlatList// FixedTouchableHighlight.js import React, { useRef } from 'react'; import { TouchableHighlight } from 'react-native'; export default function FixedTouchableHighlight({ onPress, onPressIn, ...props }) { const _touchActivatePositionRef = useRef(null); function _onPressIn(e) { const { pageX, pageY } = e.nativeEvent; _touchActivatePositionRef.current = { pageX, pageY, }; onPressIn?.(e); } function _onPress(e) { const { pageX, pageY } = e.nativeEvent; const absX = Math.abs(_touchActivatePositionRef.current.pageX - pageX); const absY = Math.abs(_touchActivatePositionRef.current.pageY - pageY); const dragged = absX > 2 || absY > 2; if (!dragged) { onPress?.(e); } } return ( <TouchableHighlight onPressIn={_onPressIn} onPress={_onPress} {...props}> {props.children} </TouchableHighlight> ); }
Thank you @Gregoirevda for providing a temporary solution 🙏 it works! I even used it as <TouchableOpacity> The issue is still present, currently on:
"react-native-pager-view": "^6.2.0",
"react-native-reanimated": "^2.10.0",
"react-native": "0.70.7",
"react-native-gesture-handler": "^2.6.0",
I just patched it react-native-tab-view/src/PanResponderAdapter.tsx
const panResponder = PanResponder.create({ onMoveShouldSetPanResponder: canMoveScreen,
// my change instead canMoveScreen
onMoveShouldSetPanResponderCapture: () => swipeEnabled,
onPanResponderGrant: startGesture,
onPanResponderMove: respondToGesture,
onPanResponderTerminate: finishGesture,
onPanResponderRelease: finishGesture,
onPanResponderTerminationRequest: () => true,
});
I hope it helps someone I applied the solution from https://github.com/facebook/react-native/issues/27355#issuecomment-981082420