react
react copied to clipboard
onMouseLeave doesn't work if the node gets detached
I have a problem with this kind of component:
class onHover extends Component {
constructor(props) {
super(props);
this.state = {
bool: false,
}
}
render() {
return (
<div onMouseEnter={() => this.setState({ bool: true })} onMouseLeave={() => this.setState({ bool: false })}>
{
this.state.bool ? (
<span>[OPTION1] show after onMouseEnter</span>
) : (
<div>[OPTION2] show after onMouseLeave</div>
)
}
</div>
)
}
}
Notice that the first option1 is a span
, option2 is a div
.
This works fine when I move the mouse slowly.
Though, if I "cut" through this with the mouse very fast, only the onMouseEnter
event gets triggered, but not the onMouseLeave
event.
It is always working though, if both options have the same tag (if both are div
or both are span
).
EDIT: I think it has something to do with rerendering. When the components are of the same type, but I force a rerender, it causes the same issues.
class onHover extends Component {
constructor(props) {
super(props);
this.state = {
bool: false,
}
}
render() {
return (
<div onMouseEnter={() => this.setState({ bool: true })} onMouseLeave={() => this.setState({ bool: false })}>
{
this.state.bool ? (
<div key={Math.random()}>[OPTION1] show after onMouseEnter</div>
) : (
<div key={Math.random()}>[OPTION2] show after onMouseLeave</div>
)
}
</div>
)
}
}
#4492 related I think.
Anyway, it's simply because the element emitting the event is replaced and it messes up the events. I could be wrong, but IIRC there's also some weird browser/spec thing involved in this behavior too (just saying).
I am new here. What I am thinking is that, can we create a new variable which stores on which tag is the mouse over, currently? As with #4492, the mouse cannot be over two elements. If we can detect if currently the mouse is over some other element, then it should automatically trigger the onMouseOut event in rest other elements, if the onMouseOver is true. Also, can we bring in another variable involving the mouse speed? Any thoughts?
Any news on this issue? As far as I see, this makes it almost impossible to write performant code with requestAnimationFrame or throttling for mouse move events. So it seems quite relevant.
What kind of news are you expecting? The browsers aren’t consistent about firing events on deleted elements. I don’t think it’s something we can fix in React, but happy to hear suggestions.
For now I’ll close as I don’t see this issue is actionable for us. If you want to avoid such problems, don’t remove/replace the hovered elements during hover.
Reopening per conversation with @sophiebits who pointed out we might be able to fix it by not relying on bubbling.
Having same issue.
@gaearon
If you want to avoid such problems, don’t remove/replace the hovered elements during hover.
How is this achievable? I am using render props and render conditionally one or another depending on hover state.
Thanks!
Couldn't you write something like so:
class onHover extends Component {
constructor(props) {
super(props);
this.state = {
bool: false,
}
this.handleMouse = this.handleMouse.bind(this)
}
handleMouse() {
this.setState((state) => ({ bool: !state.bool }))
}
render() {
return (
<div onMouseEnter={this.handleMouse} onMouseLeave={this.handleMouse}>
<div style={{display: this.state.bool ? "block" : "none"}} key={Math.random()}>[OPTION1] show after onMouseEnter</div>
<div style={{display: this.state.bool ? "none" : "block"}} key={Math.random()}>[OPTION2] show after onMouseLeave</div>
</div>
)
}
}
It looks a little bit cleaner in the return statement and I'm pretty sure you'll get the same effect you're looking for. Also try reading this section of React docs Conditional Rendering
Your idea looks cool but it's using the same onMouseLeave
as I was and I assume it would still fail on a fast mouse movement, so the bool
parameter inside this.state
would remain true
and the mouse would not be hovering the element.
I solved it by using styled-components
.
I think I might be experiencing a similar problem, but the other way around; onMouseLeave is triggered for no reason. I build a simple image carousel. When the mouse hovers over the image an onMouseEnter event is triggered, and a front overlay is rendered with controls to move to the next or previous image. When clicking the arrow to move forward or backwards every few clicks an onMouseLeave is triggered for no apparent reason.
Here is a link to a gist and screenshots.
https://gist.github.com/carpben/364e7d6c34cd0f9fa9e5ddc10181c23b
Not sure why this is happening, and what are possible solutions. Could I tell somehow if the node is destroyed and a new one is build?
Could I tell somehow if the node is destroyed and a new one is build?
I guess you can log a message inside componentWillUnmount
and see if it gets destroyed
@casvil The component in the gist isn't destroyed, I suspect it's the child styled-component that is destroyed.
Sorry, my issue does not belong here, and is related to this bug: https://stackoverflow.com/questions/45266854/mouseleave-triggered-by-click
Here's how I (almost) solved it, although I occasionally get an error "Unable to find node on an unmounted component". It's very very rare though and only seems to happen sometimes if the component is rendered when the mouse is moving over it.
...
componentDidMount() {
this._throttledHandleMouseMove = throttle(this.handleMouseMove, 200);
}
componentWillUnmount() {
this.removeMouseMoveHandler();
}
addMouseMoveHandler = () => {
addWindowEventListener('mousemove', this._throttledHandleMouseMove);
};
removeMouseMoveHandler = () => {
removeWindowEventListener('mousemove', this._throttledHandleMouseMove);
};
handleMouseMove = (e) => {
const rect = ReactDOM.findDOMNode(this).getBoundingClientRect(); // very rarely, this throws an error
if (
e.clientX < rect.left
|| e.clientX > rect.right
|| e.clientY < rect.top
|| e.clientY > rect.bottom
) {
this.setState({ isRowHovered: false });
this.removeMouseMoveHandler();
}
};
handleMouseEnter = () => {
this.setState({ isHovered: true });
this.addMouseMoveHandler();
};
handleMouseLeave = () => {
this.setState({ isHovered: false });
this.removeMouseMoveHandler();
};
render() {
return (
<div onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
My Component
</div>
);
}
...
This has been good enough for me, but if anyone has a thought as to how ReactDOM.findDOMNode(this)
can fail (in order for it to be called, I assume the component must be mounted), I'd love to know.
@mrdanimal I've tried your solution and still have the problem... I think this is a non-solvable issue for now, as it looks normal the rerendering messes these events' triggering :/
@mrdanimal @pybuche et al. in case you are still struggling, I've just published a hook that takes care of this: https://github.com/mjsarfatti/use-mouse-leave/
I had a similar problem as yours, and the hook is working well and reliably for me. If you use it please let me know if you run in any problem!
For those facing the same problem with functional components, just change useEffect to useLayoutEffect and you gona love ^^
I have a problem with this kind of component:
class onHover extends Component { constructor(props) { super(props); this.state = { bool: false, } } render() { return ( <div onMouseEnter={() => this.setState({ bool: true })} onMouseLeave={() => this.setState({ bool: false })}> { this.state.bool ? ( <span>[OPTION1] show after onMouseEnter</span> ) : ( <div>[OPTION2] show after onMouseLeave</div> ) } </div> ) } }
Notice that the first option1 is a
span
, option2 is adiv
.This works fine when I move the mouse slowly. Though, if I "cut" through this with the mouse very fast, only the
onMouseEnter
event gets triggered, but not theonMouseLeave
event.It is always working though, if both options have the same tag (if both are
div
or both arespan
).EDIT: I think it has something to do with rerendering. When the components are of the same type, but I force a rerender, it causes the same issues.
class onHover extends Component { constructor(props) { super(props); this.state = { bool: false, } } render() { return ( <div onMouseEnter={() => this.setState({ bool: true })} onMouseLeave={() => this.setState({ bool: false })}> { this.state.bool ? ( <div key={Math.random()}>[OPTION1] show after onMouseEnter</div> ) : ( <div key={Math.random()}>[OPTION2] show after onMouseLeave</div> ) } </div> ) } }
I think you missing somthing important onMouseEnter is diffrent from onMouseOver first is bubbling but second not ... in first when you hover element ( in padding area ) event triggered but again triggered when you enter mouse on his children ... so your state change twice and you think you just enter the mouse ... try onMouseOver and onMouseOut ... can you news us on result ?
The problem seems to be with React's synthetic event system, as it is inconsistent for the mouseenter/leave events. I don't know why react decided to redefine the semantics of those events, but they are more useful in their original form. If we rely on the browser's event system, things work a lot more consistent, it's a lot harder to get a missing mouseleave event; though not impossible in chrome today.
It's as simple as manually adding the event listeners before rendering the DOM:
useLayoutEffect(() => {
const mouseLeaveHandler = e => {...};
const mouseEnterHandler = e => {...};
divRef.current?.addEventListener('mouseenter', mouseEnterHandler);
divRef.current?.addEventListener('mouseleave', mouseLeaveHandler);
return () => {
divRef.current?.removeEventListener('mouseenter', mouseEnterHandler);
divRef.current?.removeEventListener('mouseleave', mouseLeaveHandler);
}
}, [divRef.current, mouseEnterHandler, mouseLeaveHandler]);
Hope this helps others.
Your idea looks cool but it's using the same
onMouseLeave
as I was and I assume it would still fail on a fast mouse movement, so thebool
parameter insidethis.state
would remaintrue
and the mouse would not be hovering the element.I solved it by using
styled-components
.
Can you please send the your solution which you have solved by using styled-components
?
This fixed the issue for me: https://github.com/facebook/react/issues/4492#issuecomment-426356566
Is there a solution? I also have the same problem
This issue has been automatically marked as stale. If this issue is still affecting you, please leave any comment (for example, "bump"), and we'll keep it open. We are sorry that we haven't been able to prioritize it yet. If you have any new additional information, please include it with your comment!
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!