There is a problem handling quick succession of touches / clicks. This can be categorised as a performance issue.
This is not a react-native specific problem but it gets compounded by the fact all touch events originate in the native's main thread, dispatched through the bridge asynchronously along with some serialisation mechanism and handled in the JS context (in a different thread).
This is very much like the problem with Android's startActivity(Intent) which is async, and, if the pushed screen takes too much time to appear, can cause this "batching" of events.
I would appreciate your thoughts on this matter and how do you think it can be handled (with minimum hacks in mind).
Some example solutions:
throttle. When you specify throttle=1000 the button will do something like _.throttle(onClick, 1000, {leading:true, trailing:false})
which just sends the first event and ignores all other touch events on this specific button in a 1000 millisecond window. See lodash throttle
This is also hacky but much simpler to maintain.
Using react-native 0.25.1.
Happens on all platforms.
I want to add a live example showing this issue and why it's relevant particularly under RN:
https://rnplay.org/apps/cKSvuA
I recommend running the example on your own physical iOS device (using "React Native Playground" from the App Store)
Use case:
Show an iOS alert when a button (TouchableOpacity) is pressed
Click the button quickly and you'll see more than one Alert
The example here simulates a stress condition in the app (JS thread being busy)
Why this isn't usually an issue in pure native implementations
In pure native code, one would show the alert from the UI thread during the click event handler. Since the alert is displayed synchronously on the UI thread, the user will not be able to tap twice (the UI thread will be blocked until the alert is shown, and when the alert is shown, the button is no longer accessible).
Why is the problem more prominent in RN?
Since the entire mechanism is now asynchronous, the touch event needs to be sent over the bridge to JS and then after business logic runs in JS, the resulting change in UI needs to be sent back over the bridge. There are two main potential bottlenecks here:
These two bottlenecks really do happen in complex apps, making the asynchronicity of the entire interaction feel worst
Where do we suffer from this issue in production?
Usually when we have one screen in the app, and the user presses a button that pushes a new screen (using some navigation library). Since the new screen pushed usually incurs a significant render, this makes the interaction sluggish on complex screens. We've had jittery users pressing multiple times in succession which results in multiple pushes.
The point of this discussion
Obviously every developer can deal with this stuff by himself using some localized band aid. Since the core of the issue is onPress events being sent multiple times when pressed very quickly, it makes sense to add some optional platform-wide mechanism to help deal with this issue. We might even want to add this optional mechanism to all core Touchable components.
how come no one shows interest in this issue?
I think it is a good idea to have throttle/debounce prop on all TouchableX. Since a touch can mean a different things, maybe a better way is to have like onPressThrottle. I think it should have a default setting of something greater than 0 to prevent most of these errors.
That's a possible solution, but there are really 2 different "creatures" in this case. The buttons inside navigation controllers - meaning, topBar buttons, FAB, menu icon etc. and the second kind is the regular JSX TouchableX views.
I'm not against adding a prop of some kind to the navigation buttons, but adding a prop to TouchableX is obviously not something that can be done. A possible solution we can have is to wrap the JSX touchables with a custom view, but then I'm not sure this should be a part of this library.
Are the buttons inside navigation controllers etc not using TouchableX components?
I started looking at the Touchable mixin, https://github.com/facebook/react-native/blob/master/Libraries/Components/Touchable/Touchable.js
and it does look at certain props of the component which has it mixed in, so I don't see why it's something that cannot be done.
Sorry, scratch that, was talking about react-native-navigation 馃槃 (got mixed up with a similar discussion)
Now I'm super confused 馃槄
I'm here because users can mash a plain react native TouchableX multiple times and it crashes our app =)
yeah sorry about that. forget my previous comment. It's a totally different issue
A prop specifying throttle of some kind is a possible solution IMO.
It's really easy creating a library that wraps the default TouchableX with another prop. However, I'm not sure this is the best solution...
Since there are some workarounds here I am going to close the issue. Since this isn't totally specific to React Native development I don't know if we'll ever have a great fix.
If anyone is interested, I've used this 'rough' solution with some success:
class Touchable extends Component {
constructor(props) {
super(props);
this.onPress = debounce(this.onPress.bind(this), 1000, {
leading: true,
trailing: false,
});
}
onPress() {
if (this.props.onPress) {
this.props.onPress();
}
}
render() {
return (
<TouchableOpacity
{...this.props}
onPress={this.props.skipDebounce ? this.props.onPress : this.onPress}
/>
);
}
}
It's not ideal but any means but it solved the most common problems I encountered.
Most helpful comment
If anyone is interested, I've used this 'rough' solution with some success:
It's not ideal but any means but it solved the most common problems I encountered.