-
Notifications
You must be signed in to change notification settings - Fork 46.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
touchstart preventDefault() does not prevent click event. #9809
Comments
Just for completeness, it works as expected on Firefox 53.0.2 on Android 6.0.1, while on chromium based browsers I still get the issue. |
@EnoahNetzach I became curious after your comment so I tested again... What I'm finding that is peculiar now is that in Chrome for Mac (58.0.3029.110) emulating touch, the second example solves the issue... but in Firefox for Mac (53.0.3) emulating touch, neither example works as desired! For the record, the Touch Events spec is clear about what should happen. |
Adding to the list, in safari on iOS 10.3 (simulated) works as expected.. |
Hmm. Bummer. Thanks everyone for digging into this. We should do the following:
Additionally, I have a PR out that attaches touch listeners local to an element. I am curious if this might have an unexpected side-effect of fixing this issue (assuming it's a React issue): |
@nhunzaker: thanks! Based on the findings so far I suspect that PR might solve the issue (except for Firefox for Mac [not mobile] where it appears to persist regardless).. Would there be an easy way to test it with my code? |
I have a custom build up for that branch, you could pull in React and ReactDOM from: React: ReactDOM: I've forked and updated your gist with those scripts here: Though I won't be able to dig into testing it myself until later today or tomorrow. |
@nhunzaker thank you. I tested just now (using Chrome for Android and Chrome with emulated touch for Mac). Unfortunately the behavior seems to be the same. |
@nhunzaker here is the non-React comparison case you asked for. https://jsfiddle.net/a2k4whf0/2/ |
@nhunzaker What's the next step here? |
It would be great if this was fixed at some point.
Thanks, that worked for me. |
Hmm.. actually "normal DOM API" solution didn't work. Had to work around that and add a flag to the touchStart handler:
And a simple return from mouseDown handler:
In this case it doesn't matter how you add "touchstart" event listener, I did it via standard React way. |
Any update on this? It's been nearly eight months and this is still an issue. I was able to work around it with a solution similar to the one suggested by @OZhurbenko, but it would be really nice to see this fixed. |
I had this same problem and was able to eliminate the superfluous render() {
return (
<div
onMouseDown={e => this.handleMouseDownOrTouchStart(e)}
onTouchStart={e => this.handleMouseDownOrTouchStart(e)}
onTouchEnd={e => e.preventDefault()}
>
Foo
</div>
);
} Reference: https://developers.google.com/web/updates/2017/01/scrolling-intervention
|
@seanmadsen that works, and is a less obtrusive workaround! But it shouldn't be necessary according to the spec. Preventing default on Any updates, React folks? 🙂 @nhunzaker? |
Sorry... I let this slide. :( I wonder if there is an event plugin that is getting in the way. I'll investigate what event plugins fire on touch. |
Thanks! |
What fun! It looks like So for some reason, this synthetic event.preventDefault event implementation isn't working: https://github.com/facebook/react/blob/master/packages/events/SyntheticEvent.js#L115-L128
Now to figure out why... |
I think I see the issue. React attaches (most) events to the document: It looks like, in Chrome, anyway, that setting This is also true of Maybe this is another reason to revisit "Attach event per react container root, rather than on the document" #2043. A change like that is serious. I wonder if there's another way we could achieve this. /cc @philipp-spiess |
@nhunzaker I think the problem here is that React adds event listeners as passive (the new default) and that passive event listeners do not allow calling Checking out your example makes this pretty clear by the following line in the console (you have to tap a few times for the message to pop up):
If I update your example and and mark the event listeners as A simple workaround in React) is to add a native event listener using the document.addEventListener(
"touchstart",
function(e) {
e.preventDefault();
},
{ passive: false }
); It's also possible to feature-detect passive-event listener support: let supportsPassiveOption = false;
const opts = Object.defineProperty({}, "passive", {
get: function() {
supportsPassiveOption = true;
}
});
try {
window.addEventListener("test", null, opts);
} catch (e) {} |
Seems like the same issue as #11530. As a workaround you can attach the event listener directly to the node via a ref. --- a/src/components/App.js
+++ b/src/components/App.js
@@ -9,12 +9,17 @@ const swallow = (e) => {
}
class Tile extends PureComponent {
+ node = React.createRef();
constructor(props) {
super(props)
this.toggle = this.toggle.bind(this)
}
+ componentDidMount() {
+ this.node.current.ontouchstart = this.toggle;
+ }
+
toggle(e) {
swallow(e)
console.log('id: ', this.props.id)
@@ -27,8 +32,8 @@ class Tile extends PureComponent {
render() {
return (
<div className={`tile`}
+ ref={this.node}
onMouseDown={this.toggle}
- onTouchStart={this.toggle}
onTouchEnd={swallow}>
<div className="box" style={{backgroundColor: this.props.id}}> </div>
</div> If you attach the listener manually then Longer term, #2043 will fix it. Since a workaround exists, and this particular issue itself is not actionable for us, I'll close this in favor of #2043 (which would solve this in the longer term). |
Got some feedback this wasn't clear enough. The problem is that Chrome made a breaking change and You can work around this by attaching a listener at the individual node level with refs. I showed how to do this in the previous comment (#9809 (comment)). Longer term, we plan to change React to attach events at the root container level instead of document level anyway. This is what #2043 is about. So when we implement #2043 the problem will go away by itself. Until then, the workaround with a ref and manual |
I am still confused why ReactJS has been broken for over a year, when @philipp-spiess has provided a trivial fix. Chrome does allow |
@mseddon My fix is not practical to be applied at the framework level. There are a number of arguments why Chrome made some events passive by default and why we don't want to revert that decision. You can read up on that here: #6436 The recommended approach is to only add non-passive events if you're certain that you will need to call That said, we would really like a declarative way to add passive/non-passive event listeners within the framework but changing that would break numerous React applications in subtle ways which is why we need to be especially careful here. We're working on a large scale revamp of React DOM called React Fire which should give us what's needed to address this issue though. |
This works, most of the time. But when I have a nested element and implement double handlers on both a parent and a child, the child's |
@gaearon is it safe to assume this old issue is fixed in React 17, via the changes to where event listeners are bound? |
Still doing preventDefault in touchstart handler does not suppress any following mouse event. Latest React. Handler attached using onTouchStart. (so passive, I presume) |
Which caused relations to annoyingly go right into edit mode on click facebook/react#9809 (comment)
I get the following error when trying to invoke Therefore, with the latest version of React, this issue's title (touchstart preventDefault() does not prevent click event.) is still an issue. |
I'm seeing this in React 18, so it seems like it isn't fixed by #2043 which I think went out with 17? I've made a reproduction of the problem with 18.2: https://4hrliz.csb.app/ In Chrome with touch device emulation I get an error in the console same as @nz-chris:
|
Do you want to request a feature or report a bug?
Bug
What is the current behavior?
Calling
e.preventDefault()
on a syntheticonTouchStart
event fails to prevent theclick
event. I also triede.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
andclick
immediately.jsfiddle
However if I move the
touchstart
listener tocomponentDidMount
and use the normal DOM API, everything works:jsfiddle
What is the expected behavior?
The first time a
touchstart
is processed, we only treat it as a hover, and wait to process theclick
event until after the nexttouchstart
. 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.
The text was updated successfully, but these errors were encountered: