I have a TouchableHighlight
wrapping a Text
block that when tapped, opens a new scene (which I\'m using react-native-router-flux).
It\'s all wo
After reading several github threads, SO articles and trying most solutions myself I have come to the following conclusions:
Providing an additional key
parameter to do "idempotent pushes" does not work consistently as of now.
https://github.com/react-navigation/rfcs/issues/16
Using debounce
slows down the UX significantly. The navigation only happens X ms after the user has pushed the button the last time. X needs to be large enough to bridge the time where double taps might happen. Which might be anything from 100-600ms really.
Using _.throttle
did not work for me. It saved the throttled function call and executed it after the timer ran out resulting in a delayed double tap.
I considered moving to react-native-navigation
but apparently the issue lies deeper and they experience it too.
So for now I built my own hack that interferes with my code the least:
const preventDoubleTapHack = (component: any, doFunc: Function) => {
if (!component.wasClickedYet__ULJyRdAvrHZvRrT7) {
// eslint-disable-next-line no-param-reassign
component.wasClickedYet__ULJyRdAvrHZvRrT7 = true;
setTimeout(() => {
// eslint-disable-next-line no-param-reassign
component.wasClickedYet__ULJyRdAvrHZvRrT7 = false;
}, 700);
doFunc();
}
};
anywhere, where we navigate instead of
this.props.navigation.navigate('MyRoute');
do
preventDoubleTapHack(this, () => this.props.navigation.navigate('MyRoute');
Beautiful.
Perhaps you could use the new disable-feature introduced for touchable elements in 0.22? I'm thinking something like this:
Component
<TouchableHighlight ref = {component => this._touchable = component}
onPress={() => this.yourMethod()}/>
Method
yourMethod() {
var touchable = this._touchable;
touchable.disabled = {true};
//what you actually want your TouchableHighlight to do
}
I haven't tried it myself. So I'm not sure if it works.
I fixed this bug by creating a module which calls a function only once in the passed interval.
Example: If you wish to navigate from Home -> About And you press the About button twice in say 400 ms.
navigateToAbout = () => dispatch(NavigationActions.navigate({routeName: 'About'}))
const pressHandler = callOnce(navigateToAbout,400);
<TouchableOpacity onPress={pressHandler}>
...
</TouchableOpacity>
The module will take care that it calls navigateToAbout only once in 400 ms.
Here is the link to the NPM module: https://www.npmjs.com/package/call-once-in-interval
What you're trying to do is you want to limit your on tap callbacks, so that they will only run ONCE.
This is called throttling, and you can use underscore
for that:
Here's how:
_.throttle(
this.thisWillRunOnce.bind(this),
200, // no new clicks within 200ms time window
);
Here's how my react component looks after all.
class MyComponent extends React.Component {
constructor(props) {
super(props);
_.throttle(
this.onPressThrottledCb.bind(this),
200, // no new clicks within 200ms time window
);
}
onPressThrottledCb() {
if (this.props.onPress) {
this.props.onPress(); // this only runs once per 200 milliseconds
}
}
render() {
return (
<View>
<TouchableOpacity onPress={this.onPressThrottledCb}>
</TouchableOpacity>
</View>
)
}
}
I hope this helps you. In case you wanna learn more check this thread.
You could bounce the click at the actual receiver methods, especially if you are dealing with the state for visual effects.
_onRefresh() {
if (this.state.refreshing)
return
this.setState({refreshing: true});
I do it like this:
link(link) {
if(!this.state.disabled) {
this.setState({disabled: true});
// go link operation
this.setState({disabled: false});
}
}
render() {
return (
<TouchableHighlight onPress={() => this.link('linkName')}>
<Text>Go link</Text>
</TouchableHighlight>
);
}