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).
Related
I tried solving this with beforeunload, unload, page visibility, etc event but nothing seems to work.
useEffect(() => {
window.addEventListener("beforeunload", alertUser);
return () => {
window.removeEventListener("beforeunload", alertUser);
};
}, []);
const alertUser = async (ev) => {
if (
isLoggedIn &&
performance.getEntriesByType("navigation")[0].type !== "reload"
) {
await logoutMethod();
}
};
I tried to handle reload case with performance navigation, but it is giving inconsistent results. Any help is appreciated, Thanks in advance. I saw some related questions/answers but nothing seems to work with the current chrome version.
Tried eventlisteners, beacon api.
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.
https://www.chromestatus.com/feature/5082396709879808
Require user gesture for beforeunload dialogs
The beforeunload dialog will only be shown if the frame attempting to
display it has received a user gesture or user interaction (or if any
embedded frame has received such a gesture). (There will be no change
to the dispatch of the beforeunload event, just a change to whether
the dialog is shown.)
Here is the problem we're running into. In our single page app, business rules dictate that we alert the user if they click the browser back button.
Which is what this code does below in our app.js:
componentDidMount = () => {
window.addEventListener('beforeunload', event => {
event.returnValue = `Are you sure you want to leave?`;
});
}
If the user navigates away on any page, the default alert box will pop up.
However in our Cypress tests we we have a beforeEach which goes back to the start of the app's flow before each test. This triggers the beforeunload event since we're leaving the page, but we don't see the alert, rather we get that chrome error:
Require user gesture for beforeunload dialogs
Anyone run into this before or have a clue on a work around?
Only thing I can think off at the moment is to remove the beforeEach but then we will need individual tests for each thing we want to test. Rather than just a few page test files...
As far as I know there's currently no way to interact with a website in a way that would qualify as a "user gesture", because Cypress currently uses programmatic browser APIs which Chrome doesn't consider as genuine user interaction (this will be possible when native events are implemented).
EDIT: re-reading the question, I'm not actually sure what you're after. If you really want to prevent the redirect, even during tests, then the following won't help. If you instead want to assert that the event was properly registered, and is doing what it's supposed to be doing, then see the below.
That being said, you don't need the unload event to be actually prevented (it's something you actually don't even want because then you'd need to manually confirm/cancel the dialog, which isn't possible ATM, although Cypress does that automatically in some cases). The callback is still called, and you can assert on that.
Thus, you can monkey-patch the event handler, cache the return value, and assert on it after the redirect:
// cypress/support/index.js
const beforeUnloadRets = [];
// command used to assert on beforeunload event return values. Callback is
// retried until it doesn't throw, and is invoked with the value
// potentially-registered beforeunload handler return value. If handler was
// registered, but didn't return anything (i.e. doesn't prevent the event),
// the value is `null`. If no handler was registered, value is `undefined`.
Cypress.Commands.add('assertBeforeUnload', ( cb ) => {
cy.wrap(null, { log: false }).should(() => cb(beforeUnloadRets.shift()));
});
beforeEach(() => {
cy.on('window:before:load', ( win ) => {
// monkey-patch `window.addEventListener` in case the `beforeunload` handler
// is registered using this API
// -------------------------------------------------------------------------
const _addEventListener = win.addEventListener;
win.addEventListener = function (eventName, listener, ...rest) {
if ( eventName === 'beforeunload' ) {
const _origListener = listener;
listener = (...args) => {
const ret = _origListener(...args);
beforeUnloadRets.push(ret === undefined ? null : ret);
return ret;
}
}
return _addEventListener.call(this, eventName, listener, ...rest);
};
// monkey-patch `window.onbeforeload` in case it's registered in that way
// -------------------------------------------------------------------------
let _onbeforeunloadHandler;
win.onbeforeunload = ( ev ) => {
if ( _onbeforeunloadHandler ) {
const ret = _onbeforeunloadHandler.call(win, ev);
beforeUnloadRets.push(ret === undefined ? null : ret);
return ret;
}
};
Object.defineProperty(win, 'onbeforeunload', {
set ( handler ) {
_onbeforeunloadHandler = handler;
}
})
});
});
Usage (note, for demonstration purposes, I register the beforeunload events inside the test, but in real scenario that's what your app will do):
describe('test', () => {
it('test', () => {
// page one. Register 1 beforeunload event, and prevent the unload event.
// -------------------------------------------------------------------------
cy.visit('/a');
cy.window().then( window => {
window.addEventListener('beforeunload', () => {
return 'one';
});
});
// redirect to page two. Assert a prevented unload event.
// Register another, but don't prevent unload.
// -------------------------------------------------------------------------
cy.visit('/b');
cy.assertBeforeUnload( ret => {
expect(ret).to.eq('one');
});
cy.window().then( window => {
// register, but don't prevent
window.onbeforeunload = () => {};
});
// page three. Assert a non-prevented unload event. Register none.
// -------------------------------------------------------------------------
cy.visit('/c');
cy.assertBeforeUnload( ret => {
// assert an event fired, but returned nothing (indicated by `null`)
expect(ret).to.eq(null);
});
// page four. Assert no beforeunload event was fired.
// -------------------------------------------------------------------------
cy.visit('/d');
cy.assertBeforeUnload( ret => {
expect(ret).to.eq(undefined);
});
});
});
We were not able to disable the Chrome action for checking if the user has interacted so we came up with a simple work around:
/* istanbul ignore next */
componentDidMount = () => {
if (process.env.NODE_ENV === 'production') {
window.addEventListener('beforeunload', onBrowserBack);
}
}
/* istanbul ignore next */
componentDidUpdate() {
// If another error modal is up, DO NOT trigger the beforeunload alert
if (process.env.NODE_ENV === 'production' && this.props.hasError) {
window.removeEventListener('beforeunload', onBrowserBack);
}
}
Basically the beforeunload alert modal will now only display in the production env, and when we are cypress testing, they will not.
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);
}
I have a application which needs to make an API call before it quits (something like logout). As I still need access to some app data (redux store) for the API call so I decided to listen to the 'before-quit' event on app.
I tried the following code:
import {remote} from 'electron';
let loggedout = false;
remote.app.on('before-quit', (event) => {
if (loggedout) return; // if we are logged out just quit.
console.warn('users tries to quit');
// prevent the default which should cancel the quit
event.preventDefault();
// in the place of the setTimout will be an API call
setTimeout(() => {
// if api call was a success
if (true) {
loggedout = true;
remote.app.quit();
} else {
// tell the user log-out was not successfull. retry and quit after second try.
}
}, 1000);
});
The event never seems to fire or preventing shutdown does not work.
When I replace before-quit with browser-window-blur the event does fire and the code seems to work.
For reference I use Electron 1.2.8 (Due to some dependencies I cannot upgrade). I've double checked and before-quit event was already implemented in that version.
Any Ideas why this event does not seem to be fired?
Thanks in advance and happy holidays!
I had the same issue, this was my solution:
In renderer:
const { ipcRenderer } = require('electron')
window._saved = false
window.onbeforeunload = (e) => {
if (!window.saved) {
callSaveAPI(() => {
// success? quit the app
window._saved = true
ipcRenderer.send('app_quit')
window.onbeforeunload = null
})
}
e.returnValue = false
}
In main:
const { ipcMain } = require('electron')
// listen the 'app_quit' event
ipcMain.on('app_quit', (event, info) => {
app.quit()
})
There are 2 problems which prevented the code from working:
Somehow the 'before-quit' event does not fire in the rerender process. (not main.js).
Once I moved the eventlistener into the main-process preventing the default did not stop the windows from closing. This can only be done through adding an window.onbeforeunload function which returns false. Like suggested in this thread.
One caveat is that the return statement of onbeforeunload does not get updated. In my case I first returned false (to prevent the closing of the window). The second time it did not return false but it kept preventing the closing of the window.
I got around that through overriding the window.onbeforeunload with a new function which did not return false.