I'm using Material-UI ClickAwayListener component with the existing code that used react-router. The button is outside of the <ClickAwayListener> ... </ClickAwayListener>
and so I expected the onClickAway
to fire before navigating to other route. But it didn't
Below are the replicate of my code, to some extent to demonstrate what I mean
function Component(){
const handleClickAway = () => {
// Do something here
}
return (
<>
<button>
<Link to="/other-route">Click here </Link>
</button>
// Some other element here
<ClickAwayListener onClickAway={handleClickAway}>
<div>
// Content
</div>
</ClickAwayListener>
</>
)
}
So if I click any where that is outside of <ClickAwayListener>
and <button>
the handleClickAway
fired, but if I click onto the <button>
which contains the like to other route it doesn't.
I tried to look onto source code of ClickAwayListener
and this part, I believe, is responsible for detecting the click
React.useEffect(() => {
if (mouseEvent !== false) {
const mappedMouseEvent = mapEventPropToEvent(mouseEvent);
const doc = ownerDocument(nodeRef.current);
doc.addEventListener(mappedMouseEvent, handleClickAway);
return () => {
doc.removeEventListener(mappedMouseEvent, handleClickAway);
};
}
return undefined;
}, [handleClickAway, mouseEvent]);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.1/umd/react-dom.production.min.js"></script>
As far as I can understand, this part will, first add an event listener to click event when the component mount/re-render and will remove that listener before the component unmount (default behavior for useEffect()
). But if this is the case, then before the component get unmount by any event that involved clicking outside of the area of ClickAwayListener
, the onClickAway
listener should be fired because the listener still attach to the click event.
So in short this is the behavior I expect:
Button click --> onClickAway
fire --> component get unmount --> go to new route --> clean-up code of useEffect() run --> the listener get removed
But this is what happen so far
Button click --> component get umount --> go to new route --> clean-up code of useEffect() run --> the listener get removed
Can someone help explain to me why this happen?
ClickAwayListener
component works by attaching the event listener to the document
, when a mouse event fires, it fires onClickAway
only when the mouse event is not inside the element.
The Link
component from react-router-dom
essentially renders something like this:
<a onClick={() => navigate()}>click</a>
When you click the link and call navigate()
, React unmounts the current component and mounts the next page component in the next frame. But the thing is, document
handlers are only processed after the next re-render, at that point, the event handler from the ClickAwayListener
had already been removed since it was unmounted, so nothing get called.
The problem can be solved by waiting until after the next re-render when the handlers from document
have been called.
<button
onClick={() => {
setTimeout(() => {
history.push("/2");
});
}}
>