react-spectrum
react-spectrum copied to clipboard
Press seems to be double triggered for overlaying buttons on chrome simulated touch
π Bug Report
Given a dialog with a close button exactly overlaying the open button (not unusual for burger-menu-style navigation), clicking the open button with chrome simulated touch, triggers the close callback of the close button directly after the open callback (in ~90% of the cases?! π) resulting in the dialog closing immediately. (See Code Sample)
(open and close refer to stately's useOverlayTriggerState setters)
π€ Expected Behavior
Clicking the open button with simulated touch in chrome only triggers open.
π― Current Behavior
Clicking the open button with simulated touch in chrome triggers open, then close immediately after.
π Possible Solution
π§
π¦ Context
We're building a "Drawer" component / mobile navigation where the open and close button are in the same screen location.
It's probably not the worst thing ever. I could not reproduce this with any form of real click or real touch input. Also Firefox simulated touch seems to work fine.
It still is kinda frustrating since chrome is our main development environment.
π» Code Sample
Here is a code sandbox showcasing the behavior.
https://codesandbox.io/s/ckmr5?file=/src/App.js
https://ckmr5.csb.app/
- Open the sandbox in chrome
- open devtools
- go to any touch simulated device preview
- press menu
π Your Environment
| Software | Version(s) |
|---|---|
| react-aria | 3.1.1 |
| react-stately | 3.0.1 |
| Browser | Chrome Version 87.0.4280.63 |
| Operating System | macOS 10.15.7 (19H15) |
π§’ Your Company/Team
π· Tracking Issue (optional)
Also note, that this only happens the first few times using the button. After a clicking for a while it magically β¨ starts working as intended - adding to my confusion.
Could you disable one of the buttons based on which one should be interacted with? or just not render it?
<Button onPress={open} isDisabled={isOpen}>MENU</Button> or {!isOpen && <Button onPress={open}>MENU</Button>}
I've also updated the sandbox to display all the events: https://codesandbox.io/s/nervous-volhard-rut48?file=/src/Button.js
which gives us this in chrome
MENU onPointerDown
MENU onTouchStart
MENU onPointerUp
MENU onTouchEnd
CLOSE onClick
which now that i'm looking at it, looks like my above suggestion wouldn't work, though i didn't try it
Soβ¦ simulated chrome touches add an additional click event a tick(? - since the overlay had time to render) laterβ¦ that seems super strange. π To circumvent the click event hitting the close button we could try to render the close button after a short timeout. That doesn't quite sound like a reasonable solution for something strange in one browsers dev mode. π
To be honest I don't know how big of a deal this is, still it is very intriguing to me. Im not familiar how react-aria handles all these events. Maybe it's a chrome bug even?
thanks for looking at it! π₯³
Yes, I believe this to be a browser bug, I've recreated it here without react or react-spectrum https://codesandbox.io/s/inspiring-lehmann-i1hjf?file=/index.html In Chrome click is fired on the new button, in Safari and FF it is not, I'll log a bug with them and post a link here
Here's the bug link https://bugs.chromium.org/p/chromium/issues/detail?id=1150073
tyvm @snowystinger - let's see how that goes. :)
Just FYI I've run into this issue on a real device, my Pixel 2. My use case has an overlay that just happens to be on top of some links. The overlay is removed when a button is clicked. The links underneath the overlay fire an unexpected click event. This is a problem in projects that use a client side router.
For now I've worked around this issue by using the deprecated onClick prop instead of onPress. I'm not really sure how that's fixed the problem but it seems to be working π€·ββοΈ .
https://codepen.io/j-arens/full/ExgjrdP
Hey, guys!
I really appreciate things youβre doing with react-aria!
Are there any plans on providing any workaround or fix inside react-aria? Seems like it add extra mess with setTimeout or any other hacks inside E2E tests :( Also spend around couple days to figure out why it does not work properly
Fixing with onClick also adds extra problems for developers to write code with react-aria.
I understand that it's a Chromium issue but it seems like react-aria tries to encapsulate different browser-specific behaviour inside
Thank you βΊοΈ
The only way we've seen to prevent the compat click event fired is to preventDefault on touch start. Unfortunately, I think this also comes with scroll prevention, so we don't have a good way to encapsulate it in usePress at the moment. We're open to suggestions or PRs to address it though.
However, what you can do, is attach your own onTouchStart (make sure it's non-passive) and preventDefault on the event See https://codesandbox.io/s/jovial-hooks-bg82y5?file=/src/Button.js for how to do that and noticed the compat 'click' is not fired.
Extra history on us trying to do exactly this https://github.com/adobe/react-spectrum/pull/2942
Is there any progress on this topic? We are running into the same problems using react-aria-components .
I set up a minimal example only using RACs: https://stackblitz.com/edit/vitejs-vite-guzxrh?file=src%2FApp.jsx
Opening the CombobBox and then selecting either Aardvark or Dog also triggers the link behind the options. The actual touch position seems to matter. If unable to reproduce, try again with a different touch position.
This problem affects both actual touch devices (Android tablets using Chrome) and emulated touch.
How would the workaround described in https://github.com/adobe/react-spectrum/issues/1279#issuecomment-1472634908 apply here as the RACs handle the events internally?
as a workaround, I added a 200ms await on the first line of the onPress handler. So hacky, so bad. But nothing else I can do:
<Button
onPress={async () => {
await sleep(200)
setIsVisible(false)
}}
>
<X size={16} weight="bold" />
</Button>
this is my hacky sleep function:
export function sleep(duration: number) {
return new Promise(resolve => setTimeout(resolve, duration))
}
Can using onClick be a valid work around for this issue? I know it says its deprecated, but I have found that it doesn't seem to have the same issues as onPress.
My worry is that this also gives up all the niceities that react-aria provides
Facing the same issue with a modal that has a Button placed exactly over a Link, where the Link was also triggered by the button onPress, thus navigating when I just wanted to confirm my cookie choices.
Adding await new Promise((resolve) => setTimeout(resolve, 0)); to the very beginning of the onPress handler solved the issue. I don't get why it works, but it does. Maybe this helps somebody that knows the library better when debugging this.
This is a really bad issue for me in my projects. When a Button is pressed via touch, the touch event is triggered a second time. Let's say the button triggers a view change with another button, the button is also triggered by the touch event. This seems to work though, probably because the handleStart() is triggered after view change in the next frame:
onPress={async(e) => e.pointerType === "touch" ? setTimeout(handleStart, 0) : handleStart()}
On a native HTML button this also works:
onTouchStart={(e) => { e.preventDefault(); handleStart(); }}
Please pass the native event to the onPress handler to solve this issue
I think this should already be fixed by #7026 which will be in the next release. Can you try the nightly version?
@devongovett can you confirm if this has been fixed? I see this behaviour on Android Chrome when I press a button in a container that collapses and immediately after the next button in the dom is pressed (not always, just sometimes). The button that is triggered afterwards is type submit
@george-roussos Can you reproduce it in a codesandbox either on the latest release or in the latest nightly version? Either I'm doing the original codesandbox incorrectly, or the bug in chrome:
Here's the bug link https://bugs.chromium.org/p/chromium/issues/detail?id=1150073
is fixed in my version.
@george-roussos Can you reproduce it in a codesandbox either on the latest release or in the latest nightly version? Either I'm doing the original codesandbox incorrectly, or the bug in chrome:
Here's the bug link https://bugs.chromium.org/p/chromium/issues/detail?id=1150073
is fixed in my version.
I tried but (of course law of demo) I haven't been able to. I will try again. But locally in my flow it happens in a multistep form, where one section a time is shown as expanded and on button press
- the first flow collapses
- it is brought to the top with
window.scroll - the second one opens
- the button press for the second one triggers
What is weird is that the second press does not seem "complete", for example when pressing it I would expect form errors to show up (the section has checkboxes and if they are not checked, errors show). The errors do not show, but I get the message Chrome shows "please tick this box if you wish to continue).
I also see similar behaviour when I click a button that opens a modal; in that case, a button inside the modal is selected (but not clicked/pressed)