Skip to content

touchstart preventDefault() does not prevent click event. #9809

Closed
@benwiley4000

Description

@benwiley4000

Do you want to request a feature or report a bug?
Bug

What is the current behavior?
Calling e.preventDefault() on a synthetic onTouchStart event fails to prevent the click event. I also tried e.nativeEvent.preventDefault(), but this didn't make any difference.

If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem via https://jsfiddle.net or similar (template: https://jsfiddle.net/84v837e9/).

Here's a div which is supposed to handle a hover case but not process a click the first time is is tapped via touch (click on desktop is fine). However tapping with touch (on a mobile device or using dev tools touch emulation) will trigger both touchstart and click immediately.

jsfiddle

const style = {
  background: 'red',
  width: 100,
  height: 100,
  // to ensure `touchstart` `preventDefault()` is allowed on mobile
  touchAction: 'none'
};

class SomeButton extends React.Component {
  constructor (props) {
    super(props);

    this.state = {
      hover: false,
      click: false
    };
  }

  render () {
    return (
      <div
        style={style}
        onMouseEnter={() => this.setState({ hover: true })}
        onClick={() => this.setState({ click: true })}
        onTouchStart={e => {
          if (!this.state.hover) {
            e.preventDefault(); // doesn't work!
            this.setState({ hover: true });
          }
        }}
      >
        {this.state.hover && 'hover!'}
        {this.state.click && 'click!'}
      </div>
    );
  }
}

However if I move the touchstart listener to componentDidMount and use the normal DOM API, everything works:

jsfiddle

// ...
class SomeButton extends React.Component {
  constructor (props) {
    // ...
  }
  
  componentDidMount () {
    this.elem.addEventListener('touchstart', e => {
      if (!this.state.hover) {
        e.preventDefault(); // WORKS!
        this.setState({ hover: true });
      }
    });
  }

  render () {
    return (
      <div
        ref={elem => this.elem = elem}
        { /* ... (removed onTouchStart) ... */}
      >
        {/* ... */}
      </div>
    );
  }
}

What is the expected behavior?
The first time a touchstart is processed, we only treat it as a hover, and wait to process the click event until after the next touchstart. If the pointer is a mouse, both events can be processed at once.

Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?
React 15.5.4. Not sure about previous React versions. Chrome for Android, Chrome for Mac emulating touch, Firefox for Mac emulating touch.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions