Detect when browser back button is pressed - ReactJS - javascript

How can I detect when back button on browser (Chrome, FireFox, Safari etc.) is clicked through JavaScript on my React website and take actions accordingly?
Also, when back button on mobile is pressed, is the event same or similar to when back button in browser is clicked?
Looking forward for an answer. Thanks in advance.

Here is a pretty simple custom hook for that:
const useBackButton = () => {
const [isBack, setIsBack] = useState(false);
const handleEvent = () => {
setIsBack(true);
};
useEffect(() => {
window.addEventListener("popstate", handleEvent);
return () => window.removeEventListener("popstate", handleEvent);
});
return isBack;
};
Working example: https://codesandbox.io/s/cranky-borg-5qwl3?file=/src/index.js
Objective approach:
constructor() {
super();
this.state = {
isBack: false
};
this.onPopstate = this.onPopstate.bind(this)
}
onPopstate() {
this.setState({ isBack: true });
alert("back!!!");
}
componentDidMount() {
window.addEventListener("popstate", this.onPopstate);
}
componentWillUnmount() {
window.removeEventListener("popstate", this.onPopstate, false);
}

Add these 2 lines into your componentDidMount().This worked for me
window.history.pushState(null, null, document.URL);
window.addEventListener('popstate', function(event) {
window.location.replace(`YOUR URL`);
});

This works some of the time, but there's no reliable way to detect stuff like this, as of now.
window.addEventListener("beforeunload",function(){
//Checking if the user hasn't clicked on something on the page
if(document.activeElement==document.querySelector("body")){console.log("Back Button Maybe?")}
})

Related

React state variables mysteriously changing before 'beforeunload' function

UPDATE:
I created a minimum reproducible sample here:
https://react-wsaei2.stackblitz.io
Editor link: https://stackblitz.com/edit/react-wsaei2
In my app I'm using a sendBeacon function in beforeunload to unlock a database document when someone closes the page without doing it themselves. Here's how I'm doing that in a useEffect with an empty array so it only runs once on start:
// Add event listener to run code before window closes
window.addEventListener("beforeunload", unlockStory);
return () => {
window.removeEventListener("beforeunload", unlockStory);
}
And here's the unlockStory function:
const unlockStory = (e) => {
e.preventDefault();
if (props.storyID && !success && !loggedOut) {
console.log("Unlocking story. success, loggedOut", success, loggedOut);
debugger;
navigator.sendBeacon(`/api/stories/${props.storyID}/${loggedOut}/${success}`, JSON.stringify({body: {locked: true}}));
}
e.returnValue = "What's going on?";
}
As you see I don't want the beacon to send every time - only if the loggedOut == false (i.e. I don't want to send the Beacon to unlock if the user is already logged out).
The problem is for some reason in the unlockStory function loggedOut is always false, even if it was true right beforehand! I put a manual refresh button on the page to check like so:
const handleRefresh = () => {
console.log("Handling refresh: success, loggedOut", success, loggedOut);
window.location.reload(false);
}
The output is:
Handling refresh: success, loggedOut false true
Unlocking story. success, loggedOut false false
WHYYY????
Another odd thing is that the debugger; line in the unlockStory function doesn't ever get triggered on normal refreshes or page closes, it only gets triggered on refreshes that are causes by me making file changes so npm automatically refreshes the open pages.
Please help, I'm at a loss, thank you!
You need to define success and loggedOut (and other used variables) as effect dependencies.
useEffect(() => {
const unlockStory = (e) => {
e.preventDefault();
console.log("Inside unlockStory. success, loggedOut:", success, loggedOut);
if (!success && !loggedOut) {
console.log("Inside if")
navigator.sendBeacon(`/api/stories/`, JSON.stringify({body: {locked: true}}));
}
e.returnValue = "What's going on?";
}
// Add event listener to run code before window closes
window.addEventListener("beforeunload", unlockStory);
return () => {
window.removeEventListener("beforeunload", unlockStory);
}
}, [success, loggedOut]); // Run on every success or loggedOut change
There is no need to manually remove event listeners as:
React also cleans up effects from the previous render before running the effects next time.
Sometimes is easier to use class based components:
class StartClass extends React.Component {
constructor(props) {
super(props);
this.state = {
loggedOut: false,
success: false,
storyObj: {
segCount: 2
}
}
}
componentDidMount() {
this.setState( { loggedOut: true } );
window.addEventListener("beforeunload", this.unlockStory);
return () => {
window.removeEventListener("beforeunload", this.unlockStory);
}
}
handleRefresh = () => {
let {success, loggedOut} = this.state
console.log("Handling refresh: success, loggedOut", success, loggedOut);
window.location.reload(true);
}
unlockStory = (e) => {
e.preventDefault();
let {success, loggedOut} = this.state
console.log("Inside unlockStory. success, loggedOut:", success, loggedOut);
if (!success && !loggedOut) {
console.log("Inside if")
navigator.sendBeacon(`/api/stories/`, JSON.stringify({body: {locked: true}}));
}
e.returnValue = "What's going on?";
}
render() {
return (
<Container fluid>
<Row>
<Col>
<p>
To reproduce:<br/> Open console, then click the Refresh page button below. When the alert pops up check the console and you'll see that the loggedOut variable has changed from true to false.
</p>
<p>
The same behavior occurs when you refresh via the browser, but you can't see the log as the console is cleared upon refresh.
</p>
{this.state.loggedOut && <Button onClick={this.handleRefresh}>Refresh page</Button>}
</Col>
</Row>
</Container>
);
}
}

How can I mimic onbeforeunload in a Vue.js 2 application?

I have a Vue component that is tracking when it is "dirty" (e.g. unsaved). I would like to warn the user before they browse away from the current form if they have unsaved data. In a typical web application you could use onbeforeunload. I've attempted to use it in mounted like this:
mounted: function(){
window.onbeforeunload = function() {
return self.form_dirty ? "If you leave this page you will lose your unsaved changes." : null;
}
}
However this doesn't work when using Vue Router. It will let you navigate down as many router links as you would like. As soon as you try to close the window or navigate to a real link, it will warn you.
Is there a way to replicate onbeforeunload in a Vue application for normal links as well as router links?
Use the beforeRouteLeave in-component guard along with the beforeunload event.
The leave guard is usually used to prevent the user from accidentally
leaving the route with unsaved edits. The navigation can be canceled
by calling next(false).
In your component definition do the following:
beforeRouteLeave (to, from, next) {
// If the form is dirty and the user did not confirm leave,
// prevent losing unsaved changes by canceling navigation
if (this.confirmStayInDirtyForm()){
next(false)
} else {
// Navigate to next view
next()
}
},
created() {
window.addEventListener('beforeunload', this.beforeWindowUnload)
},
beforeDestroy() {
window.removeEventListener('beforeunload', this.beforeWindowUnload)
},
methods: {
confirmLeave() {
return window.confirm('Do you really want to leave? you have unsaved changes!')
},
confirmStayInDirtyForm() {
return this.form_dirty && !this.confirmLeave()
},
beforeWindowUnload(e) {
if (this.confirmStayInDirtyForm()) {
// Cancel the event
e.preventDefault()
// Chrome requires returnValue to be set
e.returnValue = ''
}
},
},
The simplest solution to mimic this fully is as follow:
{
methods: {
beforeWindowUnload (e) {
if (this.form_dirty) {
e.preventDefault()
e.returnValue = ''
}
}
},
beforeRouteLeave (to, from, next) {
if (this.form_dirty) {
next(false)
window.location = to.path // this is the trick
} else {
next()
}
},
created () {
window.addEventListener('beforeunload', this.beforeWindowUnload)
},
beforeDestroy () {
window.removeEventListener('beforeunload', this.beforeWindowUnload)
}
}

Are there events for when an Electron app is shown and hidden?

I have been looking for Electron app events for when the application is shown or hidden. I see in the docs that there is 'browser-window-blur' and 'browser-window-focus' but those do not do what I want.
I would like to know when the user has switched to another application or switched back to my app. The above events get triggered if the user switches between browser windows – including the "developer's tools" window.
The code in main.js
app.on('browser-window-focus', () => {
if (mainWindow) {
console.log('browser-window-focus');
mainWindow.webContents.send('projectMsg', { "event": "focus" });
}
});
app.on('browser-window-blur', () => {
console.log('browser-window-blur');
if (mainWindow) {
mainWindow.webContents.send('projectMsg', { "event": "blur" });
}
});
It seems to me that it works exactly as you described, so maybe the requirements are different.
This code
const {app, BrowserWindow} = require('electron')
app.on('browser-window-focus', (event, win) => {
console.log('browser-window-focus', win.webContents.id)
})
app.on('browser-window-blur', (event, win) => {
if (win.webContents.isDevToolsFocused()) {
console.log('Ignore this case')
} else {
console.log('browser-window-blur', win.webContents.id)
}
})
app.once('ready', () => {
new BrowserWindow()
new BrowserWindow().webContents.openDevTools({detach: true})
})
works the following way (in 3.0.3) given that nothing is focused initially:
Clicking on window 1 prints browser-window-focus 1
Clicking on window 2 prints browser-window-blur 1 browser-window-focus 2
Clicking on devtools window prints browser-window-blur 2 Ignore this case
So as far as I see devtool is not included in these events, windows are getting blurred for any other window focused (including devtool)
There is also show and hide, though you have to explicitly show/hide the app with win.show() and win.hide() to trigger these events.
Check out of these BrowserWindow's events:
Event: 'blur': Emitted when the window loses focus.
Event: 'show': Emitted when the window is shown.
For example:
app.once('ready', () => {
let mainWindow = new BrowserWindow({show: false}) //Create main window
mainWindow.on('show', () => {
//Do something
})
})
Hope this help.

Prevent back event in javascript

I want to catch the back event in javascript, react js and show an alert message before changing the route.
I managed to catch the event wit onpopstate, but the route is changed and I want to prevent changing the route until the alert is confirmed:
window.addEventListener("popstate", (ev) => {
ev.preventDefault(ev);
alert("Are you sure")
})
Also tried with react-route, I found here some examples, but none of them worked.
import { browserHistory } from 'react-router';
componentDidMount() {
super.componentDidMount();
this.onScrollNearBottom(this.scrollToLoad);
this.backListener = browserHistory.listen(location => {
if (location.action === "POP") {
// Do your stuff
}
});
}
componentWillUnmount() {
super.componentWillUnmount();
// Unbind listener
this.backListener();
}
It behaves as in the first case, it doesn't prevent the back event.

Re-subscribing in Meteor reloads all items

I have this code:
Template.messageList.onCreated(function onCreated() {
this.state = new ReactiveDict();
this.state.setDefault({
limit: 5
});
this.autorun(() => {
this.subscribe('messages', {limit: this.state.get('limit')});
});
});
Template.messageList.helpers({
messages () {
const instance = Template.instance();
return Messages.find({}, MessagesFilter.common({limit: instance.state.get('limit')}));
}
});
Template.messageList.events({
'click .ui.button.more' (event, instance) {
const limit = instance.state.get('limit');
instance.state.set('limit', limit + 5);
}
});
It's intended to be a infinite scroll, but it presents weird events. Right now, the elements on page are reloaded when the limit changes.
Looks like meteor is re-subscribing (which is expected by the documentation) but this is reloading all the documents, it deletes the messages and then reload the with the new limit value.
How can I prevent it? I want a infinite scroll, but the way it behaves right now looks impossible.
Here's a video of what's happening.
http://recordit.co/mlsGThOdHp
Here's the repository:
https://github.com/Sornii/sayito/tree/infinite-scroll
I changed
this.autorun(() => {
this.subscribe('messages', {limit: this.state.get('limit')});
});
To
Tracker.autorun(() => {
this.subscribe('messages', {limit: this.state.get('limit')});
});
And now it works the way I want to.

Categories

Resources