Uncaught (in promise) in vue-router when multiple clicks on a button - javascript

I am working with vuejs and vue-router in Rails app. I have a button which calls a nivagate method. When user clicks on this navigate method, it hits an API enpoint via axios module and move to next component using this.$router.push({name: "RouteName"}). Problem is, when user clicks multiple on this button it gives Uncaught (in promise) undefined error in console. My guess is this error occurs because of vue-router.
My question is, how can I catch or handle this error. I am using multiple such buttons in my app so I need a generic solution.
Home.vue - component
<template>
<div>
<button
id="select_button"
#click="onSelectClick"
>
Select
</button>
</div>
</template>
<script>
export default {
onSelectClick() {
this.$`enter code here`axios.get('some-url').then(res => {
//print res on console
}).catch(err => {
// print err on console
})
this.$router.push({ name: 'route-name' }); //route-name != home
}
}
</script>
<style>
</style>
Error I get when I double(or more)-click on button
Uncaught (in promise) undefined

Error Cause: vue-router causes this error when we call this.$router.push({name: 'route-name'}) back to back multiple times without completing the previous call.
Solution: I can handle this error by adding a catch guard on router.push.
this.$router.push({name: 'router-name'}).catch(error => { //handle error here})
I made it generic by adding a method in vue mixin.
import router from '../router'
Vue.mixin({
methods: {
navigate(route) {
router.push(route).catch(err => { //handle error here });
}
}
});
Now I can use this navigate method in my components like this.
<template>
</template>
<script>
export default {
mounted() {
this.navigate({name: 'Home'})
}
}
</script>
<style>
</style>
Hope it helps others.

next({name: 'Login'})
causes Uncaught (in promise)...
I replaced it with
router.push({name: 'Login'})
return
and no error!

Related

Nuxt 3 throw createError not reaching the custom error page

I am settting up an auth middleware which runs just fine, however when I am creating the error I just get to the default error page. I need to reach the custom ~/error.vue to insert a button to click clearError()
Docs says I can just add an error component in the source, which I did.
If ~/error.vue is not reachable from ~/server/middleware then how can I call clearError() in the default error page?
in ~/server/midddleware/auth/auth.ts
export default defineEventHandler(async (event) => {
if(conditionNotMet){
throw createError({
statusCode: 404,
name: 'NotFoundError',
message: 'Todo not found',
statusMessage: "Not Found",
})
}
}
~/error.vue
<template>
<div>{{ props.error }}</div>
</template>
<script setup>
const props = defineProps(['error'])
</script>
This is what I see instead
https://nuxt.com/docs/getting-started/error-handling

How to redirect in Nuxtjs when the return query of Graphql is empty

I'm very new to Nuxtjs and javascript world. The Nuxtjs project I'm working on required fetching data from Backend (Strapi) using Graphql.
The goal is to make a redirect to homepage (index.vue) when Graphql return empty query result.
However, it did not work when I'm using router.push('/') as the code below.
Please note that I'm trying to redirect both on result and mounted block. Both of them did not work at all.
Please help me point out what I'm doing wrong here. Thank you for your kindness in advance
<script>
import getProfiletQuery from '~/apollo/queries/profiles/profile'
export default {
data () {
return {
profiles: {
}
}
},
apollo: {
profiles: {
prefetch: true,
query: getProfiletQuery,
variables () {
return { user: this.$route.params.user }
},
result ({ data }) {
if (data.profiles.length === 0) {
console.log('it enters here!!!')
this.$router.push('/') //this is not working
}
}
}
},
mounted () {
console.log('this.profiles.length: ', this.profiles.length)
if (this.profiles.length === 0) {
this.$router.push('/')
}
}
}
</script>
Old question, but the replies weren't helpful at all and no answers were offered. So in case you never found the answer, here's the best way I know of personally.
The reason your intended method didn't work is because it's unexpected behaviour for the Nuxt router. This is explained in much more detail here:
vue-router — Uncaught (in promise) Error: Redirected from "/login" to "/" via a navigation guard
There are some possible workarounds listed there, but don't use them in this use case.
What you want to do instead, is throw an error, then let Nuxt's built-in error handling take over.
Inside your result(), you need to properly call a Nuxt error like so:
result ({ data }) {
if (data.profiles.length === 0) {
this.$nuxt.error({ statusCode: 404, message: 'Page not found.' })
}
}
Then, you will need to ensure you have an error.vue template file inside your /layouts/ directory. This should contain at a bare minimum:
<template>
<div class="container">
<h1>{{ error.statusCode }}</h1>
<h2>{{ error.message }} </h2>
</div>
</template>
Sorry I didn't see this earlier, and hope you got something working in the meantime! At least now there is an answer there for whoever comes looking next!

Why is this lifecycle hook code working twice?

There is such a code:
<template>
<div class="wrapper">
</div>
</template>
<script>
import axios from 'axios';
export default{
created () {
console.log('222');
this.getTrackerIdData();
this.getTrackerData();
},
methods: {
getTrackerIdData () {
return axios.get("https://seo-gmbh.eu/couriertracker/json/couriertracker_api.php?action=tracking_new.create" , {
})
.then(response => {
this.$store.commit('tracker/setTrackingKeyId', response.data.data.tracking_new_key_id);
this.$store.commit('tracker/setQrCodeUrl', response.data.data.filename_qr_code_tracking_new);
})
.catch(function (error) {
console.log(error);
});
},
getTrackerData () {
setInterval(()=>myTimer(this), 60000);
function myTimer(th) {
return axios.get("https://seo-gmbh.eu/couriertracker/json/couriertracker_api.php?action=get_tracking_data&key_id=" + th.$store.state.tracker.trackingKeyId , {
})
.then(response => {
th.$store.commit('tracker/setTrackingServerData', response.data.data.tracking_data);
})
.catch(function (error) {
console.log(error);
});
}
},
}
}
</script>
When starting such a solution in the project, the server-side developer informed me that at least the request method getTrackerIdData () on its side works twice!
Having placed the code (console.log ('222');) in the hook of the created lifecycle (where the method calls), I found that it is displayed twice in the firebug:
Question:
Why is this happening and what approach is right in this case from the point of view of the implementation of receiving data from the server?
P.S. If everything is called in the mounted hook, then the code works, including on the server side, only 1 time.
It is important to know that in any Vue instance lifecycle, only beforeCreate and created hooks are called both from client-side and server-side. All other hooks are called only from the client-side.
so thats why created hook called 2 times and executing the console.log ('222'); twice
for reference you can read from here

Vue-Snotify notification is not showing up

I must implement the library Vue-Snotify to show notifications in a Vue.js project.
Notice: I have no pre-experience with Vue.js! I just got this task and it needs to be done.
I have played around with the project and tried different approaches, which got me to understand Vue.js a bit better but after trying so much, I'm left with no idea how to fix this :(
home.js
import Snotify from 'vue-snotify'
Vue.use(Snotify)
var vmHome = new Vue({
...
components: {
myFancyComponent,
...
}
})
myFancyComponent.vue
<template>
<div>
<button type="button" #click="showNotif">Show</button>
<vue-snotify></vue-snotify>
</div>
</template>
<script>
module.exports = {
name: "my-fancy-component",
methods: {
showNotif: function() {
console.log(this.$snotify.success('Example body content'))
}
},
...
}
</script>
I can trigger the notification method and it even returns me a valid object - no errors! But no trace of a notification.
SnotifyToast {id: 338737384058, title: null, body: "Example body content", config: {…}, eventEmitter: Vue$3, …}
Though I noticed an error in the console on the page's first load:
[Vue warn]: Error in created hook: "TypeError: Cannot read property 'emitter' of undefined"
found in
---> <VueSnotify>
<MyFancyComponent>
<Root>
TypeError: Cannot read property 'emitter' of undefined
at VueComponent.created
I wonder why the VueSnotify tag wraps the MyFancyComponent and not the other way around?
It seems that you got the methods data type wrong, it needs to be an object and not array.
module.exports = {
name: "my-fancy-component",
//---------v--- This part
methods: {
showNotif: function() {
console.log(this.$snotify.success('Example body content'))
}
},
...
}

Javascript / React window.onerror fired twice

I want to globally catch errors in my React application.
But every time the error is caught/forwarded twice to my registered function.
Example code:
window.onerror = (msg, url, lineNo, columnNo, error) => {
console.log(msg)
alert(msg)
}
class TodoApp extends React.Component {
constructor(props) {
super(props)
}
render() {
return (
<button onClick={(e)=>{
console.log("clicked")
null.bla
}}>
Create an error
</button>
)
}
}
ReactDOM.render(<TodoApp />, document.querySelector("#app"))
Here is a JS-fiddle:
https://jsfiddle.net/dmxur0rc/4/
The console only shows one 'clicked' log, so it's not the button that fires twice, but the error event.
It is known react error, related with implementation of error boundaries.
I found a basic solution to this that should work in all scenarios.
It turns out that the object is identical in all calls, you could set up something to match them exactly, or you could just attach a custom attribute to the error object...
Admittedly this may only work with window.addEventListener('error', function...), as you are given the genuine error object as an argument, as opposed to window.onerror = function... which gets the data parts, such as message & lineNumber as opposed to the real error.
This is basically how I'm using it:
window.addEventListener('error', function (event) {
if (event.error.hasBeenCaught !== undefined){
return false
}
event.error.hasBeenCaught = true
// ... your useful code here
})
If this is called with the same error twice it will exit before getting to your useful code, only executing the useful code once per error.
You need to return true from your error handler otherwise the default error handler will fire:
https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onerror
When the function returns true, this prevents the firing of the default event handler.
Also note that other error handlers may be in place via addEventHandler.
As mentioned in other answers, the problem is in React in DEV mode. In this mode it re-throws all exceptions to "improve debugging experience".
I see 4 different error scenarios
Normal JS errors (for example, from an event handler, like in the question).
These are sent to window.onerror twice by React's invokeGuardedCallbackDev.
JS errors that happen during render, and there is no React's error boundary in the components tree.
The same as scenario 1.
JS errors that happen during render, and there is an error boundary somewhere in the components tree.
These are sent to window.onerror once by invokeGuardedCallbackDev, but are also caught by the error boundary's componentDidCatch.
JS errors inside promises, that were not handled.
These aren't sent to window.onerror, but rather to window.onunhandledrejection. And that happens only once, so no problem with this.
My workaround
window.addEventListener('error', function (event) {
const { error } = event;
// Skip the first error, it is always irrelevant in the DEV mode.
if (error.stack?.indexOf('invokeGuardedCallbackDev') >= 0 && !error.alreadySeen) {
error.alreadySeen = true;
event.preventDefault();
return;
}
// Normal error handling.
}, { capture: true });
I use this error boundary to handle both React and global errors. Here is some advice from the React documentation:
Error boundaries are React components that catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI instead of the component tree that crashed.
A class component becomes an error boundary if it defines either (or
both) of the lifecycle methods static getDerivedStateFromError() or
componentDidCatch().
Only use error boundaries for recovering from unexpected exceptions;
don’t try to use them for control flow.
Note that error boundaries only catch errors in the components below them in the tree; An error boundary can’t catch an error within itself.
class ErrorBoundary extends React.Component {
state = {
error: null,
};
lastError = null;
// This lifecycle is invoked after an error has been thrown by a descendant component. It receives the error that was thrown as a parameter and should return a value to update state.
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return {
error,
};
}
componentDidMount() {
window.onerror = (msg, url, line, column, error) => {
this.logError({
error,
});
};
}
// getDerivedStateFromError() is called during the “render” phase, so side-effects are not permitted. For those use cases, use componentDidCatch() instead.
// This lifecycle is invoked after an error has been thrown by a descendant component. It receives two parameters:
// error - The error that was thrown.
// info - An object with a componentStack key containing
componentDidCatch(error, info) {
// avoid calling log error twice
if (this.lastError && this.lastError.message === this.state.error.message) {
return true;
}
// Example "componentStack":
// in ComponentThatThrows (created by App)
// in ErrorBoundary (created by App)
// in div (created by App)
// in App
// logComponentStackToMyService(info.componentStack);
this.logError({
error,
info,
});
}
async logError({
error,
info
}) {
this.lastError = error;
try {
await fetch('/error', {
method: 'post',
body: JSON.stringify(error),
});
} catch (e) {}
}
render() {
if (this.state.error) {
return display error ;
}
return this.props.children;
}
}
Another way is to store the last error's message in state and check when it happens for the second time.
export default MyComponent extends React.Component{
constructor(props){
super(props);
this.state = {message: null};
}
componentDidMount(){
const root = this;
window.onerror = function(msg, url, line, column, error){
if(root.state.message !== msg){
root.setState({message: msg});
// do rest of the logic
}
}
}
}
But anyways it is good idea to use React Error Boundaries. And you can
implement this global javascript error handling inside the error
boundary component. Where you can both catch js errors (with
window.onerror) and React errors (with componendDidCatch).
My workaround: Apply debouncing (in typescript):
import { Observable } from 'rxjs';
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
let errorObserver: any;
window.onerror = (msg, url, lineNo, columnNo, error) => {
if (!errorObserver) {
new Observable(observer => {
errorObserver = observer
}).pipe(debounceTime(300)) // wait 300ms after the last event before emitting last event
.pipe(distinctUntilChanged()) // only emit if value is different from previous value
.subscribe(handleOnError)
}
errorObserver.next(
{
msg,
url,
lineNo,
columnNo,
error
}
)
return true
}
const handleOnError = (value: any) => {
console.log('handleOnError', value)
}
This looks like it's probably firing twice due to the nature of running it on JSFiddle. In a normal build process (with webpack and babel) code with a script error like that should fail to transpile.

Categories