Reputation: 9192
We use Vue with apollo and I have difficulties to handle errors properly.
This is on of our components
<template>
<div v-if="product">
<router-view :key="product.id" :product="product" />
</div>
<div v-else-if="!this.$apollo.loading">
<p>Product is not available anymore</p>
</div>
</template>
<script>
import productQuery from "@/graphql/product.graphql";
export default {
name: "ProductWrapper",
props: ["productId"],
apollo: {
product: {
query: productQuery,
variables() {
return {
productId: this.productId,
};
},
},
},
};
</script>
If the product is not available anymore, we have three options in the backend:
a) the backend just can send null
without errors
b) send an error object as part of the data with unions
c) send some error extensions with apollo for easy error handling in the client
Option a) seems to be a strange option Option b) is too complicated for our use case. So I decided for option c):
In our backend we use apollo error extensions to send some proper errorCode for the client to handle.
{
data: { }
errors: [
{
"message": "NOT_FOUND",
"locations": ...,
"path": ...
"extensions": {
"details": [],
"errorCode": "NOT_FOUND",
"message": "NOT_FOUND",
"classification": "DataFetchingException"
}
]
}
Everything works fine as product results in null anyway as no data is sent, just an error. But vue-apollo is logging this to console.error. I don't want any logging to console.error as the user sees an red mark in his browser. But I want it to pop up in the console.error if nobody else has handled this error.
I can add an error handling in three places:
ErrorLink seems to be the wrong place as only the query in the component knows that NOT_FOUND is not fatal but can happen sometimes. Same is true for $error
So how do I say: this error might happen, I am well prepared for this. All other errors should be handled by the ErrorLink. How can I consume an error in my component?
Upvotes: 2
Views: 1516
Reputation: 9192
My overview over vue-apollo error handling.
Documentation: https://apollo.vuejs.org/api/smart-query.html
error(error, vm, key, type, options) is a hook called when there are errors. error is an Apollo error object with either a graphQLErrors property or a networkError property. vm is the related component instance. key is the smart query key. type is either 'query' or 'subscription'. options is the final watchQuery options object.
Not documented: If you return false
the error processing is stopped and the default error handler (Apollo Provider) is not called. If you return true
or do not return anything (aka undefined
) the default error handler is called.
Code Example
export default {
name: "ProductWrapper",
props: ['productId'],
apollo: {
product: {
query: productQuery,
variables() {
return {
productId: this.productId
}
},
error(errors) {
console.log("Smart Query Error Handler")
console.log(errors.graphQLErrors)
return false;
}
}
}
}
Documentation: https://apollo.vuejs.org/api/apollo-provider.html
Global error handler for all smart queries and subscriptions
Important: This is NOT for mutations.
Not documented: This is not called when an error handler of a smart query returned false
Code Example
const apolloProvider = new VueApollo({
defaultClient: apolloClient,
errorHandler: (errors) => {
console.log("Provider errorHandler")
console.log(errors)
}
});
Documentation https://apollo.vuejs.org/guide/apollo/special-options.html
$error to catch errors in a default handler (see error advanced options > for smart queries)
Code Example (wrong)
const apolloProvider = new VueApollo({
defaultClient: apolloClient,
defaultOptions: {
error(errors) {
console.log("Provider $error")
console.log(errors)
}
}
});
This was my first try, but it results in a call when the component is mounted and gives us the complete component. See https://github.com/vuejs/vue-apollo/issues/126
Code Example (kind of ok?)
const apolloProvider = new VueApollo({
defaultClient: apolloClient,
defaultOptions: {
$error() {
return (errors) => {
console.log("Provider $error handler")
console.log(errors.graphQLErrors)
}
},
},
});
This way the $error function is called. But it is called just like the default errorHandler (see above). the documentation seems to be wrong. So this method looks rather useless to me. As you can see in this debugging screenshot, it is used just like the other error handlers:
Documentation: https://www.apollographql.com/docs/react/data/error-handling/#advanced-error-handling-with-apollo-link
Code example
import {onError} from "apollo-link-error";
const errorHandler = onError(({ networkError, graphQLErrors }) => {
console.log("Link onError")
console.log({ graphQLErrors, networkError})
})
const link = split(
// split based on operation type
({query}) => {
const definition = getMainDefinition(query);
return definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
},
wsErrorHandler.concat(wsLink),
errorHandler.concat(httpLink)
);
Important: this is called before any other ErrorHandler and it works for queries, subscriptions and mutations
Documentation https://apollo.vuejs.org/guide/apollo/mutations.html
Code example 1
async submit() {
await this.$apollo.mutate({
mutation: productDeleteMutation,
variables: {
productId: this.product.id
},
}).catch((errors) => {
console.log("mutation .catch error")
console.log(errors)
})
}
Code example 2 You can use try/catch too:
async submit() {
try {
await this.$apollo.mutate({
mutation: productDeleteMutation,
variables: {
productId: this.product.id
},
})
} catch (errors) {
console.log("mutation try/catch error")
console.log(errors)
}
}
the only other handler which can be called is the onError ErrorLink (see above), which would be called before the catch error handling.
Add this lifecycle hook to your component to catch errors from mutations.
errorCaptured(err, vm, info) {
console.log("errorCaptured")
console.log(err.graphQLErrors)
return false
}
Documentation: https://v2.vuejs.org/v2/api/#errorCaptured
More links: https://medium.com/js-dojo/error-exception-handling-in-vue-js-application-6c26eeb6b3e4
It works for mutations only as errors from smart queries are handled by apollo itself.
You can have a vue default error handler to catch graphql errors from mutations
import Vue from 'vue';
Vue.config.errorHandler = (err, vm, info) => {
if (err.graphQLErrors) {
console.log("vue errorHandler")
console.log(err.graphQLErrors)
}
};
It works for mutations only as errors from smart queries are handled by apollo itself.
Documentation https://v2.vuejs.org/v2/api/#errorHandler
Errors outside vue can be handled with plain javascript
window.onerror = function(message, source, lineno, colno, error) {
if (error.graphQLErrors) {
console.log("window.onerror")
console.log(error.graphQLErrors)
}
};
This does not work for mutations, queries or subscriptions inside or outside vue components as these are handle by vue or apollo itself (and printed to console.err if not handled)
Documentation https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onerror
The only way to catch errors for mutations AND queries at the same time is onError with an ApolloLink. Server, Network and Authentication errors should be catched there as these are not specific to any operation.
Approach how to handle errors in the backend
https://blog.logrocket.com/handling-graphql-errors-like-a-champ-with-unions-and-interfaces/
Upvotes: 4