react: mouseenter fires on disabled inputs whereas mouseleave does not
There is an asymmetry to EnterLeave event plugin. Since mouseenter is created from the relativeTarget of the mouseout event it fires even though the target is disabled. Since the mouseleave is the inverse, i.e requires that the disabled element fire a mouseout, it doesn’t fire a mouseleave for the disabled element.
I am pretty sure the correct behavior here is that neither event should fire if its target is disabled, since this mirrors mouseout. No idea if none-chrome browsers have the same behavior for which mouse events fire on disabled elements.
Additional caveat I just realized, React is probably also not firing mousenter events in the case where the mouse leaves a disabled element into a non disabled element
About this issue
- Original URL
- State: closed
- Created 9 years ago
- Reactions: 26
- Comments: 39 (11 by maintainers)
Commits related to this issue
- Components: Use mouseenter for tooltip toggle Mouse events behave unreliably in React for disabled elements, firing on mouseenter but not mouseleave. Further, the default behavior for disabled elemen... — committed to WordPress/gutenberg by aduth 7 years ago
- Work around a React bug preventing tooltips from closing when attached to disabled buttons. https://github.com/facebook/react/issues/4251 — committed to Faithlife/styled-ui by bryanrsmith 4 years ago
- fix(tooltip): pointer-events disabled on button toggles A fix for #805 See https://github.com/facebook/react/issues/4251#issuecomment-267004045 — committed to contentful/forma-36 by denkristoffer 3 years ago
- fix(tooltip): pointer-events disabled on button toggles A fix for #805 See https://github.com/facebook/react/issues/4251#issuecomment-267004045 — committed to contentful/forma-36 by denkristoffer 3 years ago
- fix(tooltip): pointer-events disabled on button toggles (#816) A fix for #805 See https://github.com/facebook/react/issues/4251#issuecomment-267004045 — committed to contentful/forma-36 by denkristoffer 3 years ago
There is also a css workaroud:
Updated @CoryDanielson’s fiddle: https://jsfiddle.net/Sl1v3r/sLsut3cy/
There is also a problem for elements, containing disabled element.
onMouseEnterworks,onMouseLeavedoesn’t.Native
mouseleaveevent works as expected in the same situation.To be clear, the change in 16.13 seems to be that
mouseenterwill no longer fire for disabled inputs.So it seems
pointer-events: noneactually helps trigger the onMouseLeave event which seems counterintuitive to me but it works.Any progress or solution?
Should be fixed in 16.13. https://reactjs.org/blog/2020/03/02/react-v16.13.0.html
If not please create a new issue with a reproducing example.
@spicyj https://jsfiddle.net/qfLzkz5x/
works for me…
More on this: it works proper in Firefox but doesn’t work in Chrome and Safari
Yeah, well, finding out why this happens is not the hardest part.
Basically react doesn’t listen to
mouseleave/mouseenterevents at all (probably because they do not bubble) and instead listens only to themouseoutevent (and in one special case to themouseover, too) on the document. And uses the reverse logic (the element you moved “out” from is sent a synthetic mouseleave event and the element you’re entering (e.relatedTarget) is sent a synthetic mouseenter event).Mouseout event is typically fired when you move the cursor from any element to any element (or out of the viewport).
It also fires when you move the cursor into a
disabledbutton, this butting becoming thee.relatedTargetof the native event. That’s why react consistently sends themouseenterevent when you enter a disabled button, even if the browser doesn’t consider it a mouseenter.But when you leave the disabled button, no browser except firefox sends a mouseout event. So react doesn’t send it and we’re left with this asymmetry.
The next subtle difference is that if you wrap a disabled button with another element, chrome still doesn’t fire any mouseout events, but safari does fire a mouseout event for the wrapper element.
possible solutions?
When we move the cursor away from disabled button, all browsers fire a mouseover event. The disabled button becomes the
e.relatedTargetagain in this case, and again using the “reverse” logic we can consistently send the appropriate mouseleave event. Therefore obtaining consistent (and probably expected) behavior in all browsers.React already uses the
mouseoverevent for one special case — when mouse enters the viewport. One more special case for disabled form elements will be the least invasive solution.But it has the following cons:
mouseleaveevent on a parent element if it has the same size as the button, because chrome doesn’t send a mouseover event for it.We should attach
mouseenter/mouseleavelisteners to the node directly. I don’t know if this is possible with current Event System, but I think I remember there were talks about this concerning thescrollevents, because delegating scroll events to the document can sometimes actually be worse for performance. I personally think this solution would be the best one — it would directly reflect native browser behavior.In any case, this is basically a bug and produces inconsistency with the browser behavior. May be at least let’s decide which path to take in fixing it?
This is still broken: https://github.com/facebook/react/issues/19419 opened a new issue for it
If there were updates they would be on this issue 😉 Would you like to look into why this happens?
https://jsfiddle.net/qfLzkz5x/ doesn’t have a disabled input; if you disable it then you see the behavior I posted. That is the browser’s doing, not React’s.
If you want to capture the onClick event on a disabled input, you have to put a wrapper node around it (or listen to it at the top level, if you prefer). This matches the standard DOM behavior. I personally think this behavior is surprising and not desirable, but we find it valuable to match the DOM spec for this so we’re planning to leave it this way.
An example of when you would want
mouseleaveto fire on a disabled element is if you disabled the buttononclick.Think of a payment button:
mouseenteris called changing the hover state{hover:true}thenonclicksets state to disabled{disabled:true}, meanwhile, user moves mouse away from button and the button state is changed back to enabled{disabled:false}. The button now is in a state that is incorrect as it currently has the state{hover:true}.Any updates on this onMouseLeave issue? I’m having the same issue with almost identical code to what @andykog posted.
https://jsfiddle.net/qfLzkz5x/1/
As a workaround, I’ve updated my component to watch for the native mouseleave event on the parent of the disabled element, which seems to work, but fires more than expected. My workaround is using code similar to this:
https://jsfiddle.net/qfLzkz5x/8/
Edit: Same code with the events bound directly to the disabled button. The native mouseleave event does not work in this case, either. (on Chrome and FF)
https://jsfiddle.net/qfLzkz5x/6/
@spicyj even if I try this:
this.refs.textbox.addEventListener( 'click', clickEvent, true ); <input ref="textbox" />it’s still not working. I don’t know what kind of magic you have guys cooked up, but when it’s disabled, you are blocking every possible thing no matter whatever I do. I’m thinking about puting a global click event listener on the body and backward calculating which one of the components has been clicked from the coordinates, but that is absurd… Common…