Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
amadeus opened this issue Oct 24, 2018 · 8 comments

Comments

@amadeus
Copy link

amadeus commented Oct 24, 2018

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
Copy link

nataliemarleny commented Apr 20, 2019

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
Copy link

stale bot commented Jan 10, 2020

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 stale bot added the Resolution: Stale Automatically closed due to inactivity label Jan 10, 2020
@stale
Copy link

stale bot commented Jan 17, 2020

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!

@stale stale bot closed this as completed Jan 17, 2020
@elertan
Copy link

elertan commented Jan 17, 2020

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.

@gaearon gaearon reopened this Apr 1, 2020
@stale stale bot removed the Resolution: Stale Automatically closed due to inactivity label Apr 1, 2020
@gaauwe
Copy link

gaauwe commented Dec 2, 2020

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
Copy link

rafgraph commented May 5, 2021

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.

rafgraph added a commit to rafgraph/react-interactive that referenced this issue May 5, 2021
@inomdzhon
Copy link

inomdzhon commented Mar 29, 2022

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
// @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 (
        <div
          key={value}
          data-label={label}
          onClick={handleOptionClick}
          onMouseDown={handleOptionMouseDown}
          // ⚠️ Doesn't dispatched if sibling child re-render
          onMouseEnter={handleOptionHover}
        >
          {label}
        </div>
      );
    },
    [handleOptionClick, handleOptionHover, handleOptionMouseDown]
  );

  const dropdownContent = options.map(renderOption);

  return (
    <div>
      <div style={selectStyle}>
        {opened && searchable ? (
          <input type="text" />
        ) : (
          <div onClick={handleClick}>{selected}</div>
        )}
      </div>
      {opened && <div style={dropdownStyle}>{dropdownContent}</div>}
    </div>
  );
};

// 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 (
      <div
        key={value}
        data-label={label}
        onClick={this.handleOptionClick}
        onMouseDown={this.handleOptionMouseDown}
        // ⚠️ Doesn't dispatched if sibling child re-render
        onMouseEnter={this.handleOptionHover}
      >
        {label}
      </div>
    );
  };

  render() {
    const { options, searchable } = this.props;
    const { opened, selected } = this.state;
    const dropdownContent = options.map(this.renderOption);

    return (
      <div>
        <div style={selectStyle}>
          {opened && searchable ? (
            <input type="text" />
          ) : (
            <div onClick={this.handleClick}>{selected}</div>
          )}
        </div>
        {opened && (
          <div ref={this.scrollBoxRef} style={dropdownStyle}>
            {dropdownContent}
          </div>
        )}
      </div>
    );
  }
}

const options = [
  {
    label: "Apple",
    value: "apple"
  },
  {
    label: "Orange",
    value: "orange"
  },
  {
    label: "Bannana",
    value: "bannana"
  }
];

export const App = () => {
  const [searchable, setSearchable] = React.useState(true);
  return (
    <div>
      <label>
        <input
          type="checkbox"
          checked={searchable}
          onChange={(event) => setSearchable(event.target.checked)}
        />
        searchable
      </label>
      <SelectClass options={options} searchable={searchable} />
    </div>
  );
};

ReactDOM.render(
  <App />,
  document.getElementById("root")
);

@tianzhich
Copy link

same problem here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants