I am having difficulties testing namespaced getters although it works I am not able to test it successfully.
getContextMenuListSelectedState: function (menu) {
return this.$store.getters['appContext/getContextMenuListSelectedState'](menu)
}
<view-switcher #switchView="setCurrentView" />
setCurrentView: function (obj) {
...
let maintSelected = this.getContextMenuListSelectedState('docsmaint').label === 'maint'
...
}
is throwing an error in test:
'[Vue warn]: Error in event handler for "switchView": "TypeError: this.$store.getters**.appContext/getContextMenuListSelectedState** is not a function"
The error was quite misleading. I finally was able to stub it with a fake. After refactoring it as provided above.
function fakeGetter (menu) {
return store.state.appContext.contextMenusLists[menu].filter((item) => {
return item.selected
})
}
getContextMenuListSelectedStateSpy = sinon.stub(omsLayout.methods, 'getContextMenuListSelectedState').callsFake(fakeGetter)
Related
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:
error() inside query definition
$error() default handler for all queries
ErrorLink
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?
My overview over vue-apollo error handling.
SmartQuery error handler
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;
}
}
}
}
VueApolloProvider errorHandler
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)
}
});
VueApolloProvider $error special option (useless)
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:
ErrorLink
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
Mutation catch
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.
Mutation handled by vue with errorCaptured
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.
Mutation errors with vue global errorHandler
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
window.onerror
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
Summary
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.
Additional notes
Approach how to handle errors in the backend
https://blog.logrocket.com/handling-graphql-errors-like-a-champ-with-unions-and-interfaces/
Hi everyone here is the mixin code I wrote as I want to use this for default.vue and error.vue layout. I am trying to avoid duplicating code in two layout.
export default {
provide () {
return {
copyRight: this.getCopyrightText,
email: this.getEmail,
socials: this.getSocials
}
},
computed: {
getMenu () {
return this.store.getters['general/getMenu'].menu
},
getSocials () {
return this.store.getters['general/getSocialDetails']
},
getCopyrightText () {
return this.store.getters['general/getCopyRight']
},
getEmail () {
return this.store.getters['general/getEmail']
}
},
middleware: 'load-menu-items'
}
This is what I get: Cannot read property 'length' of undefined
What am I doing wrong?
In your component I assume you're using .length on the data you're receiving from the getter method, which is probably where the error occurs.
First of all you should debug to see if your getter is actually working as expected. Try this and look at output in console for every getter computed property. If undefined is printed to the console you'll get the error you posted if you're using .length on it
getEmail () {
let data = this.store.getters['general/getEmail'];
console.log(data);
return data;
}
If you post the component which is using this mixin maybe I can help you further.
Currently I have following Watches in Product.vue file
watch: {
isOnline: {
async handler (isOnline) {
if (isOnline) {
const maxQuantity = await this.getQuantity();
this.maxQuantity = maxQuantity;
}
}
},
isMicrocartOpen: {
async handler (isOpen) {
if (isOpen) {
const maxQuantity = await this.getQuantity();
this.maxQuantity = maxQuantity;
}
},
immediate: true
},
isSample (curr, old) {
if (curr !== old) {
if (!curr) {
console.log('send the updateCall', curr);
// this.updateProductQty(this.product.qty);
pullCartSync(this);
}
}
}
}
but I am getting this following error (Vue Warn) in console
[Vue warn]: Method "watch" has type "object" in the component definition. Did you reference the function correctly?
I'm not sure why i am getting this error, as the syntax i am using seems to be correct, and its even functioning right.
Any suggestions why its giving this warning in error console?
Update:
Location where i have used the watch in vue page.
You have something like methods: { watch: {} } in your component definition. That's why vue is complaining. That might be added by a mixin as well.
I am working on a vue project where I am using vuex for managing state. I have defined state, actions, mutations as well as dispatch to call the action. But the problem is, I am getting an error near dispatch like Cannot read property 'getProduct'(action name) of undefined at Store._callee.
Can anyone guide me where I am doing wrong?
Here is my code:
Dispatch action from component:
async created() {
try {
await this.$store.dispatch("getProduct");
} catch (error) {
console.log("Error", error);
}
}
Actions code:
Here in ProductService, I have getProduct() function where I am performing API call.
const actions = {
async getProduct({ commit }) {
commit("GET_PRODUCT", await ProductService.getProduct());
}
}
Mutation code:
const mutations = {
GET_PRODUCT: (state, product) => {
state.product = product; // Here I am mutating the state
}
}
After debugging a long time, I got to know that there was an error in service function 'ProductService.getProduct()' inside action code. Here in my case I have created service file named 'ProductService' in which I have defined functions for API calls and 'getProduct()' is one of the functions where API call is done to get list of products.
While I tried to print whether this function call is returning values or not and this was returning undefined and for that the above error was coming. Then I fixed issue in service function for which this function call returns undefined and after that it works fine.
Error was in below line of code.
commit("GET_PRODUCT", await ProductService.getProduct());
Thanks #skirtle and #Radu Diță for your comments.
Code below yields error "Cannot read property 'form' of undefined":
computed: {
boundary_limits () {
return this.$refs.hemoBoundaryLimits.form;
},
high_risk_alerts () {
return this.$refs.highRiskAlerts.form;
},
alerts () {
return {
boundary_limits: this.boundary_limits,
high_risk_alerts: this.high_risk_alerts
}
}
}
Yet if I removed alerts(), I get no error and I can even console log boundary_limits or high_risk_alerts
successfully, which means $refs.hemoBoundaryLimits and this.$refs.highRiskAlerts are defined.
So Vue.js has a problem with how I define alerts, but I see no problem in it.
Any clue what's going on?
The error comes because you are trying to access $refs in computed property.
Computed properties are evaluated before the template is mounted and thus the hemoBoundaryLimits is undefined.
You should access $refs in the mounted hook.
As solution, you can trick it by knowing when the component is mounted using #hook event:
<hemoBoundaryLimits #hook:mounted="isHemoBoundaryLimitsMounted = true" />
And in the script
data: () => ({
isHemoBoundaryLimitsMounted: false
}),
computed: {
boundary_limits () {
if (!this.isHemoBoundaryLimitsMounted) return
return this.$refs.hemoBoundaryLimits.form;
}
}