Video demonstrating issue
I have a bunch of clickable ponents that, when clicked, adds a "card" to a row. On desktop, it works fine, but on mobile (tested on iPhone, does not seem to be an issue for Android tablet), it requires 2 consecutive taps of the same button to fire the onClick
function.
These ponents also have onMouseEnter
/onMouseLeave
effects on them, to control a global state, which in turn decides if several ponents should have additional CSS applied (so I can't make it a simple CSS hover effect).
I believe that the mouse effects are interfering with the click event, but I have no idea how I could fix that. Here is the relevant code for this ponent:
const CardSource = ({ addCard, note, setHoveredNote, hoveredNote }) => {
return (
<Source
onClick={() => addCard(note)}
onMouseEnter={() => setHoveredNote(note)}
onMouseLeave={() => setHoveredNote(null)}
className={
hoveredNote && hoveredNote.index === note.index ? "highlight" : null
}
>
{note.letters[0]}
</Source>
);
};
Furthermore, once a button has been tapped twice, the hover effect CSS "sticks" to that button, and never moves to another button. This seems to happen on both iPhone and Android tablet. I would love to have this not happen anymore either.
I've created a working demonstration of this issue in a sandbox, which if viewed on mobile you should be able to recreate these issues: =/src/Components/CardSource/CardSource.js
Video demonstrating issue
I have a bunch of clickable ponents that, when clicked, adds a "card" to a row. On desktop, it works fine, but on mobile (tested on iPhone, does not seem to be an issue for Android tablet), it requires 2 consecutive taps of the same button to fire the onClick
function.
These ponents also have onMouseEnter
/onMouseLeave
effects on them, to control a global state, which in turn decides if several ponents should have additional CSS applied (so I can't make it a simple CSS hover effect).
I believe that the mouse effects are interfering with the click event, but I have no idea how I could fix that. Here is the relevant code for this ponent:
const CardSource = ({ addCard, note, setHoveredNote, hoveredNote }) => {
return (
<Source
onClick={() => addCard(note)}
onMouseEnter={() => setHoveredNote(note)}
onMouseLeave={() => setHoveredNote(null)}
className={
hoveredNote && hoveredNote.index === note.index ? "highlight" : null
}
>
{note.letters[0]}
</Source>
);
};
Furthermore, once a button has been tapped twice, the hover effect CSS "sticks" to that button, and never moves to another button. This seems to happen on both iPhone and Android tablet. I would love to have this not happen anymore either.
I've created a working demonstration of this issue in a sandbox, which if viewed on mobile you should be able to recreate these issues: https://codesandbox.io/s/mobile-requires-2-taps-i9zri?file=/src/Components/CardSource/CardSource.js
Share Improve this question edited May 19, 2020 at 18:22 damon asked May 18, 2020 at 22:13 damondamon 2,8978 gold badges28 silver badges35 bronze badges5 Answers
Reset to default 2Probably the problem with your code is, the mouse events you're using are non-bubbling. e.g. mouseenter event.
You might want to try with an event bubbling solution using onMouseOver
instead of onMouseEnter
, and onMouseOut
instead of onMouseLeave
.
const CardSource = ({ addCard, note, setHoveredNote, hoveredNote }) => {
return (
<Source
onClick={() => addCard(note)}
onMouseOver={() => setHoveredNote(note)}
onMouseOut={() => setHoveredNote(null)}
className={
hoveredNote && hoveredNote.index === note.index ? "highlight" : null
}
>
{note.letters[0]}
</Source>
);
};
Should the above NOT work, you could debug this with event type and performing event handling based on it. e.g.
const CardSource = ({ addCard, note, setHoveredNote, hoveredNote }) => {
const eventHandler = (event) => {
const { type, bubbles } = event;
switch(type) {
case "mouseover":
case "mouseenter":
setHoveredNote(note);
break;
case "mouseout":
case "mouseleave":
setHoveredNote(null);
case "click":
addCard(note);
if (bubbles) { // handle hover state
setHoveredNote(note);
}
break;
default:
break;
}
}
const onClick = (event) => eventHandler(event);
const onMouseOver = (event) => eventHandler(event);
const onMouseOut = (event) => eventHandler(event);
return (
<Source
onClick={onClick}
onMouseOver={onMouseOver}
onMouseOut={onMouseOut}
className={
hoveredNote && hoveredNote.index === note.index ? "highlight" : null
}
>
{note.letters[0]}
</Source>
);
};
Also note that, providing arrow functions as props creates new instance of the function on every render. So better use bind in that case or just function references that capture the arguments.
I think I've found the problem when I use an onClick
& onMouseEnter
& onMouseLeave
then test in the browser in mobile mode the onMouseEnter
and onClick
event fire with the first onClick
, you can add a console log to all your events and see the same behavior. The CSS style is staying because the DOM thinks that your element still has the hover attribute. If you click off of the element in question, you will see your onMouseLeave
event fire, your css will reset but the element will require two clicks again. I'm not sure what the solution is, or if its even a problem testing on an actual mobile device.
EDIT: A solution I found, is only using the onMouseEnter
& onMouseLeave
event, since this event fires onClick for mobile and I only want the hover effect on desktop the oute is what I was after.
EDIT EDIT: To maintain accessibility with the keyboard I added an onKeyDown
event to open/close the dropdown button (which is what I was working on)
onKeyDown={(event) => {
if (event.keyCode == 13) {
setShowChildren(showChildren === "hide" ? "show" : "hide");
}
}}
You could just use css hover
rather than adding a class via onMouseEnter
event, It fixes the two taps issue.
Link to sandbox
If you were to programatically use trigger for hover. You could solve the two clicks issue by using onTouchEnd
event (mented in sandbox).
Hope that helps.
I remember having a similar problem. The issue was for me that the state of ponent does not change immediately, but only upon execution of the render()
method. I believe you might have the same issue with asynchronicity for both effects you are describing.
The only render()
call I see found in your code is in App.test.js
, I usually place that in the respective ponent.tsx
.
References
- Submit button takes 2 clicks to call function in React
- React.js events need 2 clicks to execute
Use events onTouchStart
, onTouchMove
, onTouchEnd
for calculate touch.