-
Notifications
You must be signed in to change notification settings - Fork 47k
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
Not touched, and not clicked component gets a ghost mousedown event #11530
Comments
Have you tried to write similar code without React to determine if this is reproducible with just DOM APIs? |
No, sorry, I haven't. I am a beginner, just learning these things in my free time. |
I can try to handle it, if nobody do it before. |
If you'd like to help please try to create a reproducing case without React. Then we can see if it's React causing the issue or not. |
@gaearon I already asked the same question at StackOverflow. Someone has tried to reproduce the bug with jQuery only, although all attempts have failed so far. https://jsfiddle.net/3efsgd8z/3/ However, I am not sure whether the two approaches are comparable; failing to reproduce this bug with jQuery only does not mean the bug is not in Chrome. |
We should try to repro with vanilla DOM API. |
So here it is: Thats what i've found: As our issue starter made 2 functions, i also made them seperate As i investigated If we just touch first block, we can se in console, that second event invokes on the same coords, where the newblock appears at that time, so mousedown event get it as his target(and delete)
Anyway this stuff happens in vanilla js. As our issue starter said he is new to all this, i've made maximum basic code:
|
Seems like it's not a React bug then? |
Yes and i can assume even more-it is chrome specific problem, i think. |
This vanilla JS solution behaves differently in Safari (on iPhone) than the React solution: Both squares disappear in Safari with this JS implementation whereas the React solution works the way I wanted it to work. Strange. |
It is well known safari event flow-you need to use both |
@bonusrk I am sorry, I am completely lost at this point. Why is this line: in comment? In my React code I do call |
I've commented it to let this bug happens, to show that bug appears in vanilla js. And yes, if uncomment lines bug will disappear. |
I agree that the vanilla JS code indeed demonstrates a bug. The green square must not receive the events of the blue square, but it does. This is a bug in Chrome. What still confuses me is that in my React code I do call I guess the answer has something to do with synthetic events: I am calling |
No problems, man. I'll try to investigate this part in React-based code only, not just emulate it in vanila part. |
Well, i represented everything i could here:
As we can see, MouseEvent captures event preventions, but ToucheEvent doesnt, even i tried both event types prevention- on synthetic event and native.event
@gaearon btw, it seems to me that this issue have some relations with 11547 I am digging now to that part:
|
Well, i can say, that it seems, that new Chrome touch handling (which they made for performance improve), makes |
I think there are two things to be done. (1) The vanilla JavaScript code that you prepared shows a bug in Chrome. This bug won't be fixed unless somebody submits a bug report. Should I do it? (2) In the meantime, a workaround is needed. Could you expand on your idea (with code) how to "solve" this issue, please? |
@baharev updated example repo with walk-around:
I am not saying, that it is best (or at least clever) solution, but it works on Mac, Chrome 61.0.3163.100, Chrome devtools for mobile |
Great, thanks! An ugly but working solution is still better than a beautiful but not working one. My other question was: Who should submit a bug report to Chrome? |
@baharev Does it work for you? I didn't try it on Windows. |
@bonusrk I haven't tried it yet, and I cannot try it on Windows (I am on Linux). I might be able to try it on Linux later this week. Once again: Who should submit a bug report to Chrome? |
@baharev Well, i think, that you as a glorious explorer of this bug have all rights to create it and get all honor you deserved =) |
@bonusrk OK, I will do the bug report later this week, and I will let you know whether your suggested workaround works on Linux and on Android. I cannot test Windows, sorry. Many thanks for your help! |
@baharev i'll try on Window this evening and let you know. But there is no platform-specific handling in my solution, so i am 99% sure it will work everywhere. |
@bonusrk I can now confirm that your suggested workaround works on Linux (Firefox, Chrome), on iPhone (Safari), and on my Android tablet (Chrome). I think I can get away with this workaround, thanks! |
@bonusrk I submitted the bug report to the chromium project: https://bugs.chromium.org/p/chromium/issues/detail?id=788933 I greatly appreciate your help! |
@baharev You are always welcome. |
It turns out it is not a bug in Chrome: "If the contents of the document have changed during processing of the touch events, then the user agent may dispatch the mouse events to a different target than the touch events." https://bugs.chromium.org/p/chromium/issues/detail?id=788933#c6 https://w3c.github.io/touch-events/#mouse-events Although, I personally find this behavior very confusing, but apparently that's how it should be. |
The suggested workaround is feasible though a bit problematic in the real application. The difficulty is that I had to use
So where is the bug after all if it is not a bug in Chrome? If |
It does.
Calling |
@baharev It was quite confusing that you pushed the workaround to master. I cloned your repository and it took a while to figure out that I need to roll a few commits back to reproduce. :-) |
@gaearon I am very sorry for that. :( My apologies. So, what's the verdict with this issue? |
The issue appears to be caused by React using event delegation. (Which is better for performance than attaching every handler.) Here's an example with <!DOCTYPE html>
<html lang="en">
<style>
html, body {
width: 100%;
height: 100%;
}
.box {
width: 100px;
height: 100px;
margin: 10px;
touch-action: none;
}
</style>
<body>
<div>
<div class="tile">
<div class="box" style="background-color: blue"></div>
</div>
<div class="tile">
<div class="box" style="background-color: green"></div>
</div>
</div>
</body>
<script>
const tiles = document.querySelectorAll('.tile');
tiles.forEach((tile) => {
tile.addEventListener('touchstart', e => {
e.preventDefault();
tile.remove();
});
tile.addEventListener('mousedown', e => {
tile.remove();
});
})
</script>
</html> You can see that Chrome respects Here's the same example with using event delegation: <!DOCTYPE html>
<html lang="en">
<style>
html, body {
width: 100%;
height: 100%;
}
.box {
width: 100px;
height: 100px;
margin: 10px;
touch-action: none;
}
</style>
<body>
<div>
<div class="tile">
<div class="box" style="background-color: blue"></div>
</div>
<div class="tile">
<div class="box" style="background-color: green"></div>
</div>
</div>
</body>
<script>
document.addEventListener('touchstart', e => {
e.preventDefault();
e.target.remove();
});
document.addEventListener('mousedown', e => {
e.target.remove();
});
</script>
</html> You can see that Chrome doesn't respect What's interesting to me is that attaching to Regardless, the workaround is to attach a local event listener manually. #11530 (comment) is right in spirit but I don't understand why it needs to be so complicated. In React, when you want to touch the DOM manually, you can use a ref. Here's an example fix using React 16.3+ ref API: --- 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> (Note I reused Hope this helps! I'll close because there's nothing directly actionable for us, and the workaround is simple enough. |
@gaearon Perfect, thank you very much for the detailed explanation! |
We're changing React to attach events to roots in 17 which should fix it. |
@gaearon That would be great, thanks for the info! This issue is still causing me a lot of pain in my projects, even with the workaround. It would be great to have a proper solution. |
|
@gaearon I did not report it, but your workaround also solved another issue on iOS with double tap zoom. (So another issue in a different browser.) I only have preliminary results so far. I have tested React 17.0 Release Candidate in:
Obviously, this testing goes way beyond this issue, but I would like to know whether React 17 will break anything in my projects. All seems fine so far. However, please wait for the results on iOS 9.3.6. I have run into two very painful to debug issues in that environment. I won't have access to this particular device before Friday. Could you give a rough estimate when you plan to release React 17, please? I greatly appreciate your help. |
I was thinking in a week or so. |
@gaearon Manually tested the same large, non-trivial React project on:
All seems fine. Many thanks for your help, and for letting me know that this issue has been resolved. |
Thanks for verifying! |
@gaearon This is embarrassing: One thing I did not test in August was the original, minimal example, and guess what, it still shows the old, buggy behaviour. Please consider re-opening this issue. I did test some of my projects in August, but not all of them. The old bugs re-appeared after I removed your workaround in all of my projects. The latest source code of the minimal example with React 17 is here: https://github.com/baharev/ghost-click The deployment of this code, showing the buggy behaviour, is here: https://www.baharev.info/sandbox/ghost-click/index.html Expected behaviour: Clicking or touching either square should make the clicked / touched square disappear, but only that square. The following triggers the bug in Chrome, either on an Android tablet, or on my desktop machine when emulating a hand-held device. Reload the app, and touch or click the top (blue) square: Both squares disappear, which should not happen. New since 2017: I cannot get rid of the following error message in Chrome: Unable to preventDefault inside passive event listener invocation. Adding Tried in index.js:
and also in app.css:
but they seem to have no effect; this error message persists. (I double-checked in DevTools that the event listener is there, and the CSS style is also being applied.) Any help is greatly appreciated. |
@gaearon I grabbed your second code snippet from 2018, reproducing the issue with the vanilla DOM API. The only notable change is that I pass The So, your slightly modified code from 2018 is below. Try changing <!DOCTYPE html>
<html lang="en">
<style>
html, body {
background-color: lightgrey;
width: 100%;
height: 100%;
}
.box {
width: 100px;
height: 100px;
margin: 10px;
}
</style>
<body>
<div>
<div class="tile">
<div class="box" style="background-color: blue"></div>
</div>
<div class="tile">
<div class="box" style="background-color: green"></div>
</div>
</div>
</body>
<script>
document.addEventListener('touchstart', e => {
e.preventDefault();
e.target.remove();
}, {passive: false});
document.addEventListener('mousedown', e => {
e.preventDefault();
e.target.remove();
});
</script>
</html> How can I achieve something like this in React, without a ref to the underlying DOM node? In other words, how do I fix the React code, linked in my previous comment? |
The deployed (minimal, trimmed down) app is at:
http://www.baharev.info/sandbox/eventbug/
and the entire source code is at:
https://github.com/baharev/eventbug
Clicking or touching either square should make the clicked / touched square disappear, but only that square. Everything works as intended on my desktop machine both in Chrome and in Firefox. It also shows the correct behavior in Safari on iOS (and I don't care about IE or Edge).
The following triggers the bug in Chrome, either on an Android tablet, or on my desktop machine when emulating a hand-held device. Reload the app, and touch or click the top (blue) square: Both squares disappear, and in the console log I see that the not clicked, and not touched bottom green square received a spurious mousedown event (which then deleted it).
There is
touch-action: none;
in the app.css applied on the squares. If I didn't use it, I would get the following warning in Chrome when emulating a hand-held device:With
touch-action: none;
(the way it is in the deployed app), this warning goes away.The text was updated successfully, but these errors were encountered: