React Native Prevent Double Tap

Viewed 18173

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 working fine, except for the fact that if you rapidly tap on the TouchableHighlight, the scene can render twice.
I'd like to prevent the user from rapidly being able to tap that button.

What is the best way to accomplish this in Native? I looked into the Gesture Responder System, but there aren't any examples or anything of the sort, which if you're new, like me, is confusing.

12 Answers

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>
        );
    }

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});

You can do using debounce very simple way

import debounce from 'lodash/debounce';

componentDidMount() {

       this.yourMethod= debounce(this.yourMethod.bind(this), 500);
  }

 yourMethod=()=> {
    //what you actually want your TouchableHighlight to do
}

<TouchableHighlight  onPress={this.yourMethod}>
 ...
</TouchableHighlight >

I fixed using this lodash method below,

Step 1

import { debounce } from 'lodash';

Step 2

Put this code inside the constructor

this.loginClick = debounce(this.loginClick .bind(this), 1000, {
            leading: true,
            trailing: false,
});

Step 3

Write on your onPress button like this

onPress={() => this.props.skipDebounce ? this.props.loginClick : this.loginClick ()}

Thanks,

Did not use disable feature, setTimeout, or installed extra stuff.

This way code is executed without delays. I did not avoid double taps but I assured code to run just once.

I used the returned object from TouchableOpacity described in the docs https://reactnative.dev/docs/pressevent and a state variable to manage timestamps. lastTime is a state variable initialized at 0.

const [lastTime, setLastTime] = useState(0);

...

<TouchableOpacity onPress={async (obj) =>{
    try{
        console.log('Last time: ', obj.nativeEvent.timestamp);
        if ((obj.nativeEvent.timestamp-lastTime)>1500){  
            console.log('First time: ',obj.nativeEvent.timestamp);
            setLastTime(obj.nativeEvent.timestamp);

            //your code
            SplashScreen.show();
            await dispatch(getDetails(item.device));
            await dispatch(getTravels(item.device));
            navigation.navigate("Tab");
            //end of code
        }
        else{
            return;
        }
    }catch(e){
        console.log(e);
    }           
}}>

I am using an async function to handle dispatches that are actually fetching data, in the end I'm basically navigating to other screen.

Im printing out first and last time between touches. I choose there to exist at least 1500 ms of difference between them, and avoid any parasite double tap.

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

This worked for me as a workaround

import React from 'react';
import {TouchableOpacity } from 'react-native';

export default SingleClickTouchableOpacity = (props) => {
let buttonClicked = false
return(
    <TouchableOpacity {...props}  onPress={() => {
        if(buttonClicked){
            return
        }
        props.onPress();
        buttonClicked = true 
        setTimeout(() => {
            buttonClicked = false
          }, 1000);
    }}>
        {props.children}
    </TouchableOpacity>
)
}
Related