Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React: onClick on Mobile (iPhone) requires 2 taps?

Video demonstrating issue

I have a bunch of clickable components 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 components also have onMouseEnter/onMouseLeave effects on them, to control a global state, which in turn decides if several components 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 component:

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

like image 862
damon Avatar asked May 18 '20 22:05

damon


2 Answers

Probably 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.

like image 98
pritam Avatar answered Nov 11 '22 20:11

pritam


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 outcome 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");
          }
        }}
like image 20
Shane Walker Avatar answered Nov 11 '22 19:11

Shane Walker