react icon indicating copy to clipboard operation
react copied to clipboard

Bug: pseudo styles don't work properly if React state does not change when clicking with right mouse button.

Open acanthite1855 opened this issue 3 years ago • 11 comments

I have a button that changes background in :hover and :active states. Clicking the button with left mouse button (LMB) works fine, styles change as they should. But, clicking with right mouse button (RMB) works fine only if underlying react state is actually changed (e.g. different from the previous state)

React version: 17.0.2

Steps To Reproduce

  1. Open the code example provided below
  2. Click the "Click Me" button with LMB more than 2 times (DON'T move your mouse cursor while clicking)
  3. Click the "Click Me" button with RMB more than 2 times (DON'T move your mouse cursor while clicking)

Link to code example: https://codesandbox.io/s/pensive-sunset-z1t69f

The current behavior

No mater how many times you click with LMB, the "Click Me" button consistently changes its background color to yellow when you hold LMB down, and changes it back to silver when you release LMB.

The second time RMB pressed down, the background color of the "Click Me" button remains yellow, even if you release RMB. That is, whenever you click it with RMB and the new react state is the same as the previous, the button style remains yellow even if you release RMB.

The expected behavior

Clicking the "Click Me" button with RMB should respect styles regardless of the underlying react state.

acanthite1855 avatar Feb 16 '22 00:02 acanthite1855

I tried to repro the issue in the sandbox but I couldn't. Is there any other condition that needs to be met to repro this, for example browser? Could you link a video showing the bug?

sammy-SC avatar Feb 16 '22 14:02 sammy-SC

It's Chrome 98.0.4758.102 (64-bit) on Windows 10

acanthite1855 avatar Feb 16 '22 17:02 acanthite1855

And here is some demo video. I've set up recording software so that, when I click LMB it riples green circle, and for RMB click it riples blue circle. Notice how the color changes from silver to yellow and back when I click with LMB. Notice how the color stays yellow after the second click with RMB, as if I was holding RMB, but I don't, I just repeat clicking.

https://user-images.githubusercontent.com/52529496/154323076-47ad9a8c-984e-43d9-ba78-227aec9d2c90.mp4

acanthite1855 avatar Feb 16 '22 17:02 acanthite1855

Yes, this is not a React bug, e.preventDefault() prevents the right-click menu from popping up, but also prevents the button from popping up, so it's still yellow, just use a class instead.

[CodeSandbox] https://codesandbox.io/s/black-field-nt3ueu?file=/src/App.js

import { useState } from 'react';
import './styles.css';

export default function App() {
  const [text, setText] = useState('none');

  const mousedown = e => {
    e.target.classList.add('active');
    if (e.nativeEvent.button === 0) setText('left');
  };
  const mouseup = e => {
    e.target.classList.remove('active');
  };
  const contextmenu = e => {
    e.preventDefault();
    setText('right');
  };

  return (
    <>
      <div>
        <button onMouseDown={mousedown} onMouseUp={mouseup} onContextMenu={contextmenu}>
          Click Me
        </button>
      </div>
      <div>{text}</div>
    </>
  );
}
button {
  background-color: gray;
}

button:hover {
  background-color: silver;
}

/* button:active {
  background-color: yellow;
} */
button.active {
  background-color: yellow;
}

ouweiya avatar Mar 01 '22 15:03 ouweiya

Can't reproduce with Chrome Version 98.0.4758.102 (Official Build) (64-bit) on Ubuntu 20.04.

What is the reason you're using event.nativeEvent and not event directly?

eps1lon avatar Mar 01 '22 16:03 eps1lon

@ouweiya but why does the original code snippet work fine on Mac + Chrome, and as @eps1lon says, on Ubuntu + Chrome? Is it bugged on Mac and Ubuntu then?

@eps1lon the reason is to destinguish between left and right mouse click. But it does not matter. It does not work even if I use event.type like this:

e.preventDefault();
if (e.type === "click") {
  setText("left");
} else if (e.type === "contextmenu") {
  setText("right");
}

I didn't test it on linux, but I can tell you for sure it is reproducing on Windows 10 x64 (tried 3 different machines). It's chrome 99.0.4844.51 now and the issue still persists.

acanthite1855 avatar Mar 11 '22 23:03 acanthite1855

I've tried it in vanilla JS to find out if it's a bug in Chrome itself. Turns out it's not. Here is an example that works perfectly. https://codesandbox.io/s/unruffled-raman-ydki4c So, it's for sure a bug in react.

const button = document.getElementById("button");
const text = document.getElementById("text");

console.log("loaded");

function handleClick(e) {
  e.preventDefault();
  if (e.button === 0) {
    text.innerText = "left";
  } else if (e.button === 2) {
    text.innerText = "right";
  }
}

button.onclick = handleClick;
button.oncontextmenu = handleClick;

acanthite1855 avatar Mar 12 '22 00:03 acanthite1855

I tested with vanilla JS on a windwos 11 computer and found that this error triggers randomly. It works fine when clicking on the edge of the button after refreshing the page, but not working when clicking on the center of the button after refreshing the page.

ouweiya avatar Mar 12 '22 12:03 ouweiya

So, it's for sure a bug in react.

Note that this could still be a browser bug if it's not implementing the spec correctly on which React may rely.

Can reproduce in Chrome Version 98.0.4758.102 on Windows 11

eps1lon avatar Mar 12 '22 18:03 eps1lon

You're right. I was too fast with this verdict, sorry.

acanthite1855 avatar Mar 12 '22 20:03 acanthite1855

I tried to reproduce this in using both React and Vanilla JS. Turned out that the button color doesn't update when the page is not rendered again.

You can get the same behavior using Vanilla JS by adding this on the script you mentioned:

function handleClick(e) {
  // ...
-  } else if (e.button === 2) {
+  } else if (e.button === 2 && text.innerText !== "right") {
    text.innerText = "right";
  }
}

It seems to happen because in Vanilla JS the element is still re-rendered when the value is not changed. I couldn't find any workarounds, though.

I'm using Opera GX 89.0.4447.104 on Windows 10.

ramos-ph avatar Aug 24 '22 14:08 ramos-ph