facebook / react

The library for web and native user interfaces.
https://react.dev
MIT License
228.5k stars 46.76k forks source link

onMouseEnter does not fire on an underlaying element if an element above is removed #13956

Open amadeus opened 5 years ago

amadeus commented 5 years ago

Do you want to request a feature or report a bug?

Bug - I did do some searching around the issues to see if there was a similar/dupe, but I could not find one.

What is the current behavior?

With 2 elements overlaying on top of each other, if the upper element gets removed while the cursor is over both elements, mouse enter never fires on the element below. I compared this to native browser events and the issue does not appear to persist there (native browser events appear to fire mouse enter for the underlying div when the overlaying div gets removed).

If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem.

CodeSandbox Example

I provided a top level boolean constant to switch between using react's synthetic events and the native browser events. In the console I keep track of state updates as console logs. The simple way to test - open the console, mouse over the upper div in a position that is also on top of the lower div, click to remove the upper div, the lower div SHOULD fire mouse enter. It does not with synthetic events, but it does with browser events.

What is the expected behavior?

Expected behavior for me would be if react would fire mouse enter on the underlaying div when the upper div is removed.

Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?

"dependencies": {
    "react": "16.5.2",
    "react-dom": "16.5.2",
  },

I have not had a chance to test previous versions.

nataliemarleny commented 5 years ago

If replicated in pure HTML without React, this behaves completely differently than the React examples above. Pure HTML behaviour seems to match more with my expectations than React's behaviour (with or without circumventing synthetic events).

Simplest to see the codesandbox and compare (styles.css is reused from React examples).

stale[bot] commented 4 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contribution.

stale[bot] commented 4 years ago

Closing this issue after a prolonged period of inactivity. If this issue is still present in the latest release, please create a new issue with up-to-date information. Thank you!

elertan commented 4 years ago

I'm facing a similar problem. I have a list with elements that represent entities, on hover I want to show actions for these entities, such as a delete action. When I delete an entity, the list moves up to fill the 'gap' that it would have created, placing the cursor on top of the next element, however this does not trigger the onMouseEnter event on that element, thus not showing the actions.

As far as I'm aware React does not provide other API's that would allow me to capture an event that the cursor has entered this element when the DOM's element change position instead of the user moving the mouse. I have also tried onMouseOver, but that also only triggers after the user having to move the mouse.

gaauwe commented 3 years ago

On my website I'm using onMouseEnter for tooltips, since this is fired while hovering on desktops but only after clicking on touch devices. By doing this I'm facing a similar problem where onMouseEnter is not fired on touch devices, if a Portal is closed before this action (and thus removing the Portal node from the DOM).

In my case it would be opening an overlay using a Portal, closing the overlay by either clicking the background or a close button and then opening a tooltip (by using onMouseEnter).

I created a reproducible example of this: https://codesandbox.io/s/sweet-shtern-0dm47, where it's important to load the example as a touch device. If you open the overlay, close the overlay by clicking it and then click the log button it won't log anything on touch devices. After closing the overlay you first have to click another part of the page, before the onMouseEnter will trigger.

I'm guessing the onMouseEnter doesn't trigger because internally in React it depends on the onMouseOut event of the removed node. I tried simulating mouse events after closing the overlay to get the mouse back in the document, which didn't work. Using the native window.addEventListener('mouseenter', logFunction) does work as expected.

rafgraph commented 3 years ago

The onMouseEnter event also isn't dispatched when a new element replaces an old element (this also applies to onPointerEnter too). See this codesandbox and check the console: https://codesandbox.io/s/react-mouseenter-bug-vq1zg?file=/src/App.js

Note that the onMouseOver (and onPointerOver) event dispatches correctly, so one solution is to use onMouseOver instead of onMouseEnter and make the event handler idempotent.

I ran into this when creating React Interactive and replaced onMouseEnter with onMouseOver to solve it.

inomdzhon commented 2 years ago

Same problem.

Case

When some sibling child re-render, the onMouseEnter event doesn't dispatched.

Reproduce

⚠️ Please, turn on mobile view in DevTools

https://codesandbox.io/s/facebook-react-issues-13956-onmouseenter-doesnt-dispatched-if-sibling-child-re-render-iurgns

image

Image 1. ✅ Work correctly if sibling child doesn't re-render

image

Image 2. ❌ Work incorrectly if sibling child re-render

Code backup, if the codesandbox link expires ```javascript // @ts-nocheck import React from "react"; const selectStyle = { padding: "8px 12px", border: "1px solid" }; const dropdownStyle = { padding: "8px 12px", border: "1px solid tomato" }; // Example with funciton component const SelectFC = ({ options, searchable }) => { const [opened, setOpened] = React.useState(false); const [selected, setSelected] = React.useState("choose"); const handleClick = React.useCallback(() => { setOpened(true); }, []); const handleOptionClick = React.useCallback((event) => { console.log("[handleOptionClick]"); setSelected(event.currentTarget.dataset.label); setOpened(false); }, []); const handleOptionHover = React.useCallback(() => { console.log("[handleOptionHover]"); }, []); const handleOptionMouseDown = React.useCallback((event) => { console.log("[handleOptionMouseDown]"); event.preventDefault(); }, []); const renderOption = React.useCallback( ({ label, value }) => { return (
{label}
); }, [handleOptionClick, handleOptionHover, handleOptionMouseDown] ); const dropdownContent = options.map(renderOption); return (
{opened && searchable ? ( ) : (
{selected}
)}
{opened &&
{dropdownContent}
}
); }; // Example with class component class SelectClass extends React.Component { scrollBoxRef = React.createRef(); state = { opened: false, selected: "choose" }; open = () => { this.setState(() => ({ opened: true })); }; close = () => { this.setState(() => ({ opened: false })); }; handleClick = () => { this.state.opened ? this.close() : this.open(); }; handleOptionClick = (event) => { console.log("[handleOptionClick]"); const label = event.currentTarget.dataset.label; this.setState(() => ({ selected: label })); this.close(); }; handleOptionHover = () => { console.log("[handleOptionHover]"); }; handleOptionMouseDown = (event) => { console.log("[handleOptionMouseDown]"); event.preventDefault(); }; renderOption = ({ label, value }) => { return (
{label}
); }; render() { const { options, searchable } = this.props; const { opened, selected } = this.state; const dropdownContent = options.map(this.renderOption); return (
{opened && searchable ? ( ) : (
{selected}
)}
{opened && (
{dropdownContent}
)}
); } } const options = [ { label: "Apple", value: "apple" }, { label: "Orange", value: "orange" }, { label: "Bannana", value: "bannana" } ]; export const App = () => { const [searchable, setSearchable] = React.useState(true); return (
); }; ReactDOM.render( , document.getElementById("root") ); ```
tianzhich commented 1 year ago

same problem here