I want a functional React component to close whenever the user clicks outside the application as well as outside the component within the application.
I've worked out how to close the component so long as the user clicks inside the containing application, but it still ignores clicks in an entirely different Windows application.
I want to emulate the behavior of the "Avatar" button at the top right of a Google Maps window. The button opens a modal dialog that is dismissed when the user clicks anywhere outside the dialog.
My current code has a useEffect hook that calls addEventListener and removeEventListener on the current document:
useEffect(() => {
const handleClickOutside = (event) => {
if (wrapperRef.current && !wrapperRef.current.contains(event.target)) {
onCloseButtonClick(event)
}
};
document.addEventListener("click", handleClickOutside, true);
return () => {
document.removeEventListener("click", handleClickOutside, true);
};
}, [onCloseButtonClick]);
I had hoped the solution was as simple as attaching addEventListener and removeEventListener on window instead of document, but it doesn't work.
How can I clear the visible property of the component when the user clicks outside the containing application?
Thanks in advance for your attention.
You probably need to also listen to onblur on the window. That can call the onCloseButtonClick straight away as theres no element attached to those events.
useEffect(() => {
const handleClickOutside = (event) => {
if (wrapperRef.current && !wrapperRef.current.contains(event.target)) {
onCloseButtonClick(event)
}
};
document.addEventListener("click", handleClickOutside, true);
window.addEventListener("blur", onCloseButtonClick, true);
return () => {
document.removeEventListener("click", handleClickOutside, true);
window.removeEventListener("blur", onCloseButtonClick, true);
};
}, [onCloseButtonClick]);
Related
React
I'm developing a popup and I want to close it down with both a button and the esc keydown.
However, even if I use the same function for both, some reason the window.location.replace() part inside the keydown function just doesn't seem to work.
everything else(console log / removing cookies) work inside the function, just not the window.location.replace('url')
I really need to use only the replace function to turn the popup off(directing into a new url with no going back), why would this happen?
Is there some things to look out for keydown I don't know about?
useEffect(() => {
window.addEventListener('keydown', handleKeyDown)
return () => {
window.removeEventListener('keydown', handleKeyDown)
}
}, [])
const temp = () => {
Cookies.remove('rurl', { domain: '.domain.io' })
Cookies.remove('rurl')
console.log(window.location.pathname)//for testing
window.location.replace(window.location.pathname)
console.log(2)//this is for testing. this gets printed but not the replace
}
const handleKeyDown = async (e) => {
if (e.keyCode !== 27) {
return
}
temp()
}
Got no ideas.. I tried searching for things to look out while useing keydown, but none were found
Even though I tried changing replace to href it wouln't work either.(I need to use replace though)
There are many versions of the click outside hooks out there that will attach a "mousedown" or "touchstart" event listener to the document and close a dropdown or modal window when you click outside...
The problem is if you have many dropdowns using the same hook you end up with the same events attached to the document multiple times.
How to prevent that? and does it matter as long as you remove them when you unmount the components?
I've already researched and there doesn't seem to be a way to check whether a dom element already has an event attached to it.
An example taken from: useOnClickOutside
const useOnClickOutside = (ref, handler) => {
useEffect(() => {
const listener = event => {
// Do nothing if clicking ref's element or descendent elements
if (!ref.current || ref.current.contains(event.target)) {
return;
}
handler(event);
};
const escape = event => {
if (event.keyCode === 27) {
handler(event);
}
};
// these event listeners pile up on the document if you have several component using that hook
document.addEventListener('mousedown', listener);
document.addEventListener('touchstart', listener);
document.addEventListener('keydown', escape, false);
return () => {
document.removeEventListener('mousedown', listener);
document.removeEventListener('touchstart', listener);
document.removeEventListener('keydown', escape);
};
}, [ref, handler]);
};
Here using that hook on 5 different dropdowns on the page, assigns the same event listener 5 times to the document
The best practice to do this is using useEffect cleanup function
const handleMouseDown = e => {
console.log(e)
}
useEffect(() => {
document.addEventListener("mousedown", handleMouseDown)
// cleanup
return () => {
document.removeEventListener("mousedown", handleMouseDown)
}
}, [])
By passing the empty array [] to the useEffect you guarantee that the event listeners are registered only in the initial mounting of the component. By using the cleanup function, you avoid registering it more than once.
if we are calling this component multiple times then in that case also on each and every mount event will be attached to the document.
Possible to write useEffect with event listener on top of the component and call handler method from anywhere then it won't happen.
I want a function, to send a request to a server (don't care about response) before a user refreshes the page.
I've tried using the componentWillUnmount approach but the page refresh doesn't call this function. e.g.
import React, { useEffect } from 'react';
const ComponentExample => () => {
useEffect(() => {
return () => {
// componentWillUnmount in functional component.
// Anything in here is fired on component unmount.
// Call my server to do some work
}
}, []) }
Does anyone have any ideas how to do this?
You could try listening for the window.beforeunload event.
The beforeunload event is fired when the window, the document and its
resources are about to be unloaded. The document is still visible and
the event is still cancelable at this point.
useEffect(() => {
const unloadCallback = (event) => { ... };
window.addEventListener("beforeunload", unloadCallback);
return () => window.removeEventListener("beforeunload", unloadCallback);
}, []);
Note: This will respond to anything that causes the page to unload though.
Note 2:
However note that not all browsers support this method, and some
instead require the event handler to implement one of two legacy
methods:
assigning a string to the event's returnValue property
returning a string from the event handler.
You can do this, almost, by checking if the page has been refreshed.
Source: https://developer.mozilla.org/en-US/docs/Web/API/Navigation_timing_API
Example:
if (window.performance) {
console.info("window.performance is supported");
console.info(performance.navigation.type);
if (performance.navigation.type == performance.navigation.TYPE_RELOAD) {
console.info( "This page is reloaded" );
} else {
console.info( "This page is not reloaded");
}
}
All examples I am seeing online are using React Components. I am a newbie to react. So any explanation will be helpful, and what I should do to achieve this.
export default function Review() {
...
useEffect(() => {
console.log("useeffect called")
window.addEventListener("beforeunload", save());
}, []);
...
return (...);
Here is a CodeSandbox.io link. Here you will find that I have 2 pages 1 home the other dashboard.
When I go to dashboard. I get the Alert. But Leaving Dashboard I do not get the Alert Message. beforeunload is not working how I expect it to.
https://codesandbox.io/s/react-router-basic-forked-8uvvi?file=/
An alternate way to call a function when a page/component is about to be unmounted is to add it as a return from your useEffect(). For example:
useEffect(() => {
return(() => save())
}, []);
CodeSandbox Example
You are not passing an onbeforeunload callback, you are immediately invoking the alert.
useEffect(() => {
console.log("useeffect called");
// subscribe event
window.addEventListener("beforeunload", alert("HI"));
}, []);
Create/define a callback that does one, or more, of the following:
Prevents the default on the event object
Assigns a string to the event object's returnValue property
Returns a string from the event handler.
window.beforeunload
Note: To combat unwanted pop-ups, some browsers don't display prompts created in beforeunload event handlers unless the page has
been interacted with. Moreover, some don't display them at all.
Code suggestion
useEffect(() => {
const handler = (e) => {
// Cancel the event
e.preventDefault(); // If you prevent default behavior in Mozilla Firefox prompt will always be shown
// Chrome requires returnValue to be set
e.returnValue = "";
// ... business logic to save any data, etc... before the window unloads
save();
return "";
};
// subscribe event
window.addEventListener("beforeunload", handler);
return () => {
window.removeEventListener("beforeunload", handler);
};
}, []);
My problem is that I need the user to confirm if he wants to continue to refresh the page. If he press No, it won't refresh the page.
Kindly take a look at my development so far:-
useEffect(() => {
window.addEventListener("beforeunload", alertUser);
return () => {
window.removeEventListener("beforeunload", alertUser);
};
}, []);
If you want to display a sort of confirmation before leaving the page then follow the beforeunload event guidelines
According to the specification, to show the confirmation dialog an
event handler should call preventDefault() on the event.
However note that not all browsers support this method, and some
instead require the event handler to implement one of two legacy
methods:
assigning a string to the event's returnValue property
returning a string from the event handler.
To combat unwanted pop-ups, browsers may not display prompts created
in beforeunload event handlers unless the page has been interacted
with, or may even not display them at all.
The HTML specification states that calls to window.alert(),
window.confirm(), and window.prompt() methods may be ignored during
this event. See the HTML specification for more details.
I just tested this in chrome and safari and it works. I don't have a windows box, but this should cover most cases.
useEffect(() => {
const unloadCallback = (event) => {
event.preventDefault();
event.returnValue = "";
return "";
};
window.addEventListener("beforeunload", unloadCallback);
return () => window.removeEventListener("beforeunload", unloadCallback);
}, []);
I think you are looking for this.
https://dev.to/eons/detect-page-refresh-tab-close-and-route-change-with-react-router-v5-3pd
Browser Refresh, Closing Tab, and back and forward buttons, are all explained.
Try this:
useEffect(()=>{
const unloadCallback = (event) => {
const e = event || window.event;
//console.log(e)
e.preventDefault();
if (e) {
e.returnValue = ''
}
return '';
};
window.addEventListener("beforeunload", unloadCallback);
return () => {
//cleanup function
window.removeEventListener("beforeunload", unloadCallback);
}
},[])