Best Practice in Error Handling in Vuejs With Vuex and Axios
Asked Answered
D

8

29

I am using Vuex + axios, I want to know the best practice in handling errors for vuex + axios. What I am doing now is that when I request using axios and it returns an error, it will be committed in mutation and update my state. What I want to do is, If there's an response error from my request it will return to my component so that I can handle the error much faster.

Like in angular, there's a dependency injection and the response will return to the component.

Dettmer answered 7/2, 2018 at 6:3 Comment(0)
H
27

Have your cake and eat it too. Assuming you are already using an interceptor...

axios.interceptors.response.use(function (response) {
  return response;
}, function (error) {
  store.commit('ERROR', error) // just taking some guesses here
  return Promise.reject(error) // this is the important part
})

This will keep the promise rejection going back to the caller so in your component, something like...

axios.whatever(...).then(res => {
  // happy days
}, err => {
  // oh noes!
})
Headsman answered 7/2, 2018 at 6:12 Comment(12)
is it always required to use axios inside Action in vuex for submitting the form? Or I can use axios in my component then pass the data to Action in vuex to mutate?Dettmer
@Dettmer I'm sorry, I'm really not sure what you're asking. Questions should go in your post above, not in the commentsHeadsman
I've got Uncaught (in promise) TypeError: Cannot read property 'version' of undefined error at return Promise.reject(error)Scilla
@MKatleast3 I suggest you open a new question. There is no such code in my answerHeadsman
@Headsman Am I the only one to see return Promise.reject(error) // this is the important part lol? Should I import Promise?Scilla
@MKatleast3 Promise is a global object. See developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…. I was referring to your comment "Cannot read property 'version' of undefined"; there is no code in my answer that uses .version. Again, I suggest you open a new question if you're having trouble with your codeHeadsman
@Headsman what is the advantage of this approach over commiting "ERROR" in error function handler of axios err => { store.commit('ERROR', error) }? I assume most people want commit fine-granular errors like ERROR_GET_BILL, ERROR_CREATE_ACCOUNT, etc.Settera
@Settera OP was asking for a generic "catch-all" way to commit an error when it happens as well as handle the error specifically in their components. Using the interceptor above allows you to do exactly that. There's nothing stopping you catching the error further down the promise chain.Headsman
@Headsman Is it possible to pass in context of the module calling the service so we may target that specific modules error field? Example - a getAllUsers action fails, so the interceptor sets error on the users module.Absolution
@Absolution I'm not entirely sure what you mean. As above, you can handle errors specifically in whatever initiated the Axios call with .catch()Headsman
@Headsman Is it a good idea to have a global mixin with a computed property that looks after the error in the vuex store and if it exists display some sort of modal ?Skolnik
@Ibris from what I know of global mixins (and that's not very much), they're generally not a good idea at any time. I'd rather handle general errors at the App levelHeadsman
Z
6

I just use the catch. The same thing I was using before I switched to vuex. It's probably the most universal and well documented solution and lets me continue to insert my errors into the html of the components like I was doing before. It also lets me continue to use my loading = true, loading = false html animation.

So I end up with 3 state properties, data, error, and loading. It seems to work for me. Your mileage may vary. I am also using vuex modules and namespacing but here is a simplified example without that

//somevuexstore.js

actions: {

fetchData(context) {

    axios
        .get("api/someendpoint")
        .then(response => {
            context.commit('loading')
            context.commit('organizations', response.data)

        }).catch(error => {
            console.log(error.response.data.message || error.message)
            context.commit('error', error)
        });
},

mutations: {
organizations(state, data) {
    return state.organization = data
},

error(state, data) {
    return state.error = data
},

loading(state) {
    return state.loading = false
},

state= {

organization: [],
error: '',
loading: true
}

Then in my component.vue it's very similar to the way I was doing it before, just with the added computed properties.

computed: {
...mapState({
        getError: 'error',
        getLoading: 'loading',
        getAllOrg: 'organization',
}),
}

mounted() {
      this.$store.dispatch('fetchData')
}

And my html would be stuff like this.

<tr v-for="value in getAllOrg" :key="value.id">
   <td>{{ value.id }}</td>
   <td>{{ value.email }}</td>
   <td>{{ value.name }}</td>
   <td>{{ value.['created-at'] | formatDate }}</td>
</tr>

I insert the error messages where appropriate

<div v-if="getError" class="error">
   <p>{{ getError }}</p>
</div>

For loading animation I use vue spinners package inserted into html where appropriate.

<div v-if="getLoading" style="height:37px;">
    <p>
      <bar-loader class="custom-class" color="#c2c2c2" 
      getLoading="getLoading" 
      :width="130"></bar-loader>
   </p>

Zalucki answered 13/5, 2020 at 3:35 Comment(0)
A
5

Let me tell you the approach, I used for error logging is this. By this you can handle all vue error by on code.

window.onerror = function (message, source, lineno, colno, error) {
  /// what you want to do with error here
};

This is a global error handler for the browser. If any error comes uncaught that can be handle by this.

Also, if you want to handle your error. You can do this.

axios.get('/user?ID=12345')
  .then(function (response) {
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
      // when you throw error this will also fetch error.
       throw error;
  });

If you want to look on vue for error handling you can go for. https://v2.vuejs.org/v2/api/#errorHandler

Vue.config.errorHandler = function (err, vm, info) {
  // handle error
  // `info` is a Vue-specific error info, e.g. which lifecycle hook
  // the error was found in. Only available in 2.2.0+
}

Let me give you a link where window.onerror is used

https://github.com/stacktracejs/stacktrace.js/

Anhydrous answered 7/2, 2018 at 6:11 Comment(0)
A
1

You can use event bus, like this

import Vue from 'vue'

export const EventBus = new Vue();

and then trigger error

axios.get(...)
  .catch(function (error) {
     EventBus.$emit('error', error)
  });
Ab answered 2/12, 2019 at 16:1 Comment(0)
E
1

Now available in VUE 3 composition API uniquely tackled

we might use AXIOS interceptors as documentation to set up the desired config, then in VUE

import {onErrorCaptured, ref} from "vue";

setup(){
    let errors = ref(null)
    onErrorCaptured((error)=>{
         // checking for server response first
         errors.value = error.response?Object.values(error.response.data)[0]:error.message
    })
    return{errors}
}
Elson answered 11/7, 2021 at 11:56 Comment(0)
C
0

I have come to the conclusion that they can not always exists general methods for handling errors, so they must be somehow coupled to the context. Is a good thing to have separate api files, but mediate this with the mention above. I have separate api files and I am doing the following:

//comments-api.js
export default {
    get (url, handler){
        //return the promise to further possible chains
        return axios.get(url)
            .then( response => handler.success(response.data) )
            .catch( error => handler.serverDownOrUnexpected(error.response) )
    },
}
//comments.js - vuex module
import $comments from './../../services/api/comments-api'
...
actions: {
    $comments.get(url, {
        success: (data) => commit('success_handler', data),
        serverDownOrUnexpected: (error) => commit('unexpected', error)
        //so on...
    })
}
...

in this approch, whenever I want to change the way certain errors are handled, I have to make changes just in one place, plus benefits of decoupled code.

Cerallua answered 31/3, 2019 at 21:56 Comment(0)
S
0

The power of promisses! (plus async/await)

vue method (mycomponent.js)

async YourAsyncMethod() {
    const payload = {key: "var"}
    const result = await axios
        .post('/your/api/endpoint', payload)
        .catch(e => {
            console.log(e.message)
        });
}

yourMethod() {
    // start axios logic
    const payload = {key: "var"}
    axios
        .post('/your/api/endpoint', payload)
        .then(response => {
            console.log(response.data)

            // start state action logic
            this.$store
                .dispatch('yourAction', payload)
                .then(add => {
                    console.log('success mutation!');
                })
                .catch(error => {
                    // error = Error object,
                    console.log('error mutation:',error.message);
                    console.log(error) // to se full error object
                });
        })
        .catch(error => {
            console.log('error axios request', error.data)
        });
}

with state actions (store/actions.js)

yourAction(){
    const some_logic = false;
    if (!some_logic) {
        // when return a Promisse.reject
        //you can get error with catch(e) from youtMethod
        return Promise.reject(new Error("Impressora já adicionada"))
    }
    context.commit('MUTATION_METHOD', payload);
}

with axios

http
    .post('/your/api/endpoint', payload)
    .then(response => {
        console.log(response.data)
    })
    .catch(error => {
        console.log('error', error.data)
    });
Swacked answered 5/10, 2019 at 1:37 Comment(0)
F
0

Add "last load" state into Vuex, and watch for changes in root.

This may look heavy and complicated, but it's logical and separates components well.

Bonus: you'll know instantly if you have loaded your data and was the attempt successful!

Fairish answered 23/12, 2020 at 8:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.