This question already has answers here:
window.onbeforeunload not displaying the alert box
(2 answers)
Closed 4 months ago.
I hope you are doing well!
I am trying to catch the window close or tab close or refresh event in my project and I tried all possible solutions but haven't succeeded.
I tried using:
useEffect(() => {
return () => {
window.alert("Alert");
};
});
and I tried:
useEffect(() => {
window.onbeforeunload = () => {
window.alert("alert");
};
return () => {
window.onbeforeunload = null;
};
});
which seems to only trigger if I have my window in the background for a while.
and I tried:
window.addEventListener("onbeforeunload", () => {
window.alert("alert");
});
but haven't been able to capture it.
I will use this functionality to send data to a specific API whenever the user closes the window or tab or refreshes (and possibly turns off the PC while on the page if that is event possible). But all these methods weren't working for me.
Is there any other way or is there a reason they aren't working?
Thank you for your time!
You might need to call preventDefault, and I think the event is called beforeunload, try:
window.addEventListener("beforeunload", ev => {
ev.preventDefault()
return (ev.returnValue = "Are you sure you want to close?")
})
When registering event listeners, you should do this with useEffect so you properly remove the listeners.
useEffect(() => {
const onUnload = (e: any) => {
e.preventDefault()
return (e.returnValue = "Are you sure you want to close?")
}
window.addEventListener("beforeunload", onUnload)
return () => window.removeEventListener("beforeunload", onUnload)
}, [])
Some things to know about beforeunload:
It does not call blocking functions such as alert, prompt or confirm. It is evident from a user perspective.
And it is fired only if there has been ANY user interaction with the site. Without ANY interaction (even one click anywhere) event beforeunload won't be fired.
It is impossible to catch a tab/browser close and even if it was, it was not going to be reliable as the user might force close the browser or kill the process, etc.
So the best option I found, I wrapped in a NPM package here: https://www.npmjs.com/package/#garage-panda/use-before-unload
Take a look and let me know if it works for you.
This is how you use it:
const setEnabledBeforeUnload = useBeforeUnload({
initEnable: false, // do you need to be enabled by default
onRefresh: () => {
// the page has been refreshed (the user has clicked Reload)
},
onCancel: () => {
// the user has clicked Cancel
}
});
And the only possible (and the correct way) to know if the user has left your page is to actually catch the event when he lands on your page (e.g. the sessionStorage is empty).
I'm wondering how I can run a method when a window or tab closes. I've tried using the following...
window.onclose = () => {
// execute function
}
window.onbeforeunload = () => {
// execute function
}
but both of these only fire when I refresh the page not when I close it.
Any help would be very appreciated thanks!
Not possible anymore.
Newer browsers doesn't support it anymore.
I'm using Next.js to build a simple static site. The only vanilla JS script I have is for a mobile menu - to toggle it open and add a class to the body to prevent scrolling:
if (process.browser) {
document.addEventListener('DOMContentLoaded', function() {
let mainNav = document.querySelector('#menu')
let navBarToggle = document.querySelector('#toggle')
let noScroll = document.querySelector('body')
navBarToggle.addEventListener('click', function () {
mainNav.classList.toggle('active')
navBarToggle.classList.toggle('close')
noScroll.classList.toggle('lock-scroll')
})
})
}
The if process.browser statement is something I had to add to get it to work on localhost, which works but only on the first load, e.g. when I first run next dev. After I navigate to another page it won't load, I guess because the page isn't being fully re-rendered, which the script needs or something?
Then when I deploy the site the toggle isn't working at all, even on first load.
Anyway, any help in getting this working would be much much appreciated!
process.browser has been deprecated, use type of window
if (typeof window !== "undefined") {
document.addEventListener('DOMContentLoaded', function() {
let mainNav = document.querySelector('#menu')
let navBarToggle = document.querySelector('#toggle')
let noScroll = document.querySelector('body')
navBarToggle.addEventListener('click', function () {
mainNav.classList.toggle('active')
navBarToggle.classList.toggle('close')
noScroll.classList.toggle('lock-scroll')
})
})
}
if that solution doesn"t work, you can use dynamic import
I open a new tab in the browser with const w = window.open('www.example.com'); and I get the DOM with let d = w.document; Then I go to another webpage with an click-event: d.querySelector('a').click(); The new webpage opens, in the same window, and I want to grap the DOM of the just openend page by running d = w.document again. And this is the point where I get stuck.
I aspect the DOM of the currently openend window, but instead I get the DOM of the previous window back. When console.log(w) (in the .js itself not in the console), with the new webpage open, I get the window object of the current page and the window.document matches the DOM of the openend page. But as I said in reality I get the DOM of the previous page back when I run d = w.document on the new page.
The following code is the whole function I use. Sidenote: I didn't(/couldn't) use window.document.onload fom I reason I don't understand, but It seems I can't attach an function to the window onload event. Also to make things clear my code is inside of a ES6 Class so I didn't use the constas in the example.
The Problem:
When I run the getDOM method on the new openend webpage it returns the DOM of the previous page.
async foo() {
await this.getDOM()
.then(w => this.d = w.document);
const x = this.d.querySelector('button.x');
console.log(send); //null
}
getDOM () {
return new Promise(resolve => {
const interval = setInterval(() => {
if (this.w.document.readyState === 'complete') {
clearInterval(interval);
resolve(this.w);
}
}, 500);
});
}
I hope someone can help, thanks in advance!
i know that there are hundreds of questions like: "How can i prevent close event in electron" or something like that.
After implementing a confirmation box (electron message box) in the beforeunload event i was able to close my app and cancel the close event. Since the dev tools are always open, i didn't recognize that it doesn't work while the dev tools are closed...
window.onbeforeunload = e =>
{
// show a message box with "save", "don't save", and "cancel" button
let warning = remote.dialog.showMessageBox(...)
switch(warning)
{
case 0:
console.log("save");
return;
case 1:
console.log("don't save");
return;
case 2:
console.log("cancel");
return false;
// e.returnValue = "false";
// e.returnValue = false;
}
};
So, when the dev tools are opened, i can close the app with saving, without saving and cancel the event.
When the dev tools are closed, the cancel button doesn't work anymore.
Btw.:
window.onbeforeunload = e =>
{
return false;
alert("foo");
};
will cancel the close event and obviously wouldn't show the message (doesn't matter if dev tools are open or closed)
window.onbeforeunload = e =>
{
alert("foo");
return false;
};
will cancel the close event after pressing ok if dev tools are open and will close the app after pressing ok if dev tools are closed
Intentionally i'm using the synchronous api of the message box and while i'm writing this question i figured out that a two windowed app (new remote.BrowserWindow()) will behave exactly like with the dev tools.
Has anyone an idea how i can resolve this problem?
Many thanks in advance
Instead of onbeforeunload prefer working with the event close. From this event, you'll be able to catch the closing event before the whole closure process is completed (event closed). With close, you'll be able to take the control and stop whenever you need the completion of the closure.
This is possible when you create your BrowserWindow, preferably in the main process:
// Create the browser window.
window = new BrowserWindow({});
// Event 'close'
window.on('close', (e) => {
// Do your control here
if (bToStop) {
e.preventDefault();
}
})
// Event 'closed'
window.on('closed', (e) => {
// Fired only if you didn't called e.preventDefault(); above!
})
In addition, be aware that the function e.preventDefault() is spreading in the whole code. If you need to be back to the natural behaviour of Electron, you need to toggle the variable e.defaultPrevented to false.
Actually, it seems e.preventDefault() function is handling the variable e.defaultPrevented to true until any change on it.
Maybe this will help someone with similar needs as i had, i have a react app wrapped in an electron app, the react app is agnostic to electron and can also run in the browser and the requirements i had was to show the default browser prompt, the infamous Leave Site? alert.
In the browser this is easy, for example with react i just do this:
useEffect(() => {
window.onbeforeunload = promptOnProjectLeave ? () => true : undefined;
return () => {
window.onbeforeunload = undefined;
}
}, [promptOnProjectLeave]);
Which will show the default browser Leave Site? prompt, but in electron this will only prevent the window from being closed without any action prompt asking you if you are sure, so my approach was a mix of this post and another post.
This is the solution
mainWindow.webContents.on('will-prevent-unload', (event) => {
const options = {
type: 'question',
buttons: ['Cancel', 'Leave'],
message: 'Leave Site?',
detail: 'Changes that you made may not be saved.',
};
const response = dialog.showMessageBoxSync(null, options)
if (response === 1) event.preventDefault();
});
This will allow me to use window.onbeforeunload in my react code as i would in the browser, in the browser i will get the default browser prompt and in electron i will get a message box :)
This is my first time working with electron so might be some ways to improve this but either way hope this helps someone, i know it would have helped me when i started with this task.
Update:
As I mentioned above in the comments of the accepted answer, the preventDefault was ignored on Windows. To be precise, I had it placed in the callback of a native electron dialog that opened when the user closed the app.
Therefore I have implemented a different approach:
let close: boolean = false
win.on('close', (ev: any) => {
if (close === false) {
ev.preventDefault()
dialog.showMessageBox({
type: 'warning',
buttons: ['Cancel', 'Ok'],
title: 'Do not forget to safe your changes',
cancelId: 0,
defaultId: 1,
noLink: true
}).then((val) => {
if (val.response === 0) {
// Cancel the close process
} else if (win) {
close = true
app.quit()
}
})
}
})
You can simply use 'pagehide' event. It seems to be working fine for electron apps. It works slightly different from 'beforeunload' as it can't prevent closing window/tab, but if you only need to do something before the page is closed(send some async request with navigator.sendBeacon(), etc) then this event might suit your needs.
You can read more info about it here, here and in the docs
Example of usage:
window.addEventListener('pagehide', () => {
window.navigator.sendBeacon(url, data);
}