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.
Related
Someone, please help! 🙏
answerCall event is not firing in Android only. When we call displayIncomingCall and the incoming call shows on Android, and when we press the call end button, the endCall event is firing, but when we press the call-accept button, it stays there on the call screen and nothing happens. And the answerCall event is not firing.
Please help, we've tried all the solutions, nothing is working.
And this is my code:
// Register background handler
messaging().setBackgroundMessageHandler(async remoteMessage => {
console.log('Message handled in the background!', remoteMessage);
const callUUID = uuid.v4().toLowerCase()
const number = String(Math.floor(Math.random() * 100000))
RNCallKeep.displayIncomingCall(callUUID, number, number, 'number', false);
// await Linking.openURL("awesome://");
DeviceEventEmitter.addListener("answerCall", (payload) => {
console.log('answerCall', payload); // it doesn't run here
});
});
AppRegistry.registerComponent(appName, () => App);
This very helpful block, it helps me to implement. you can try this
https://blog.theodo.com/2021/03/react-native-incoming-call-ui-callkeep/
Register your event emitter outside the fcm background messaging class
I had to kill the native UI by calling the endCall method then navigate to the respective screen
messaging().setBackgroundMessageHandler(async remoteMessage => {
//your call trigger code piece
})
RNCallKeep.addEventListener('answerCall', async ({ callUUID }) => {
RNCallKeep.endCall(callUUID);
RNCallKeep.backToForeground();
RootNavigation.navigate("Call", {callUUID});
});
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?")}
})
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>
);
}
}
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)
}
}
Suppose I have pageA where I listen for a firebase document changes
export default {
mounted() {
this.$f7ready(() => {
this.userChanges();
})
},
methods: {
userChanges() {
Firebase.database().ref('users/1').on('value', (resp) => {
console.log('use data has changed');
});
}
}
}
Then I go to pageB using this..$f7.views.current.router.navigate('/pageB/')
If on pageB I make changes to the /users/1 firebase route I see this ,message in the console: use data has changed, even though I'm on a different page.
Any way to avoid this behavior, maybe unload the page somehow?
I tried to stop the listener before navigating away from pageA using Firebase.off() but that seems to break a few other things.
Are you properly removing the listener for that specific database reference? You'll have to save the referenced path on a dedicated variable to do so:
let userRef
export default {
mounted() {
this.$f7ready(() => {
this.userChanges();
})
},
methods: {
userChanges() {
userRef = Firebase.database().ref('users/1')
userRef.on('value', (resp) => {
console.log('use data has changed');
});
},
detachListener() {
userRef.off('value')
}
}
}
That way you only detach the listener for that specific reference. Calling it on the parent, would remove all listeners as the documentation specifies:
You can remove a single listener by passing it as a parameter to
off(). Calling off() on the location with no arguments removes all
listeners at that location.
More on that topic here: https://firebase.google.com/docs/database/web/read-and-write#detach_listeners