Accessing VUE JS's data from Axios

14.3k views Asked by At

I have a Vue JS (Vuetify) App that makes an ajax request that I would like to populate a div's content with the response, however I am having difficulties accessing the instance's data. All examples I have seen use this to point to the data object, but when I do I get this error

Unable to set property 'message' of undefined or null reference

The app is quite simple:

main.js:

import Vue from 'vue'
import App from './App.vue'
import Vuetify from 'vuetify'


Vue.use(Vuetify)

new Vue({
  el: '#app',
  render: h => h(App)
})

App.vue

export default {
  data () {
    return {
    ....
    message: '',
    order: {},
    ...
  },
  methods: {
    send: function() {
      axios.post(this.api+"orders",this.order).then(function(response) {
        this.message = "Your payment was successful";
        ...
      }
   }
 }

this.order is accessible without a problem with Axios' post method however the anonymous function that handles the promise returned seems to have a problem accessing this.message, in contrary to the examples I have seen.

What is it that I am doing differently here?

2

There are 2 answers

3
Aluan Haddad On

Your problem is this line

axios.post(this.api+"orders",this.order).then(function(respo‌​nse) {

Examples may use this as you say however, by using a second level of nested function expression, you are accessing a different dynamic this than you think you are.

Basically, send is the method of the Vue object, but since this is not lexically scoped inside of function expressions, only inside of => functions, you have the wrong this reference in the callback you are passing to Promise.prototype.then.

Here is a breakdown:

methods: {
  send: function() {
    // here: `this` technically refers to the `methods` object
    // but Vue lifts it to the entire view object at runtime
    axios.post(this.api + "orders", this.order)
      .then(function(response) {
        // here: `this` refers to the whatever object `the function is called on
        // if it is called as a method or bound explicitly using Function.prototype.bind
        // the Promise instance will not call it on anything
        // nor bind it to anything so `this` will be undefined
        // since you are in a module and modules are implicitly strict mode code.
        this.message = "Your payment was successful";
      });
    }
 }

Try this instead

export default {
  data() {
    return {
    message: "",
    order: {},
  },
  methods: {
    send: function() {
      // here: `this` technically refers to the `methods` object
      // but Vue lifts it to the entire view object at runtime
      axios.post(this.api + "orders", this.order).then(response => {
        // here: this refers to the same object as it does in `send` because
        // `=>` functions capture their outer `this` reference statically.
        this.message = "Your payment was successful";
      });
    }
  }
}

or better yet

export default {
  data() {
    return {
    message: "",
    order: {},
  },
  methods: {
    async send() {
      const response = await axios.post(`${this.api}orders`, this.order);
      this.message = "Your payment was successful";
    }
  }
}

Note in the second example, which uses JavaScript's recently standardized async/await functionality, we have abstracted away the need for a callback entirely so the point becomes moot.

I suggest it here, not because it relates to your question, but rather because it should be the preferred way of writing Promise driven code if you have it available which you do based on your use of other language features. It leads to clearer code when using Promises.

The key point of this answer however, is the scoping of the this reference.

3
Ikbel On

I can think of these solutions for your problem.

1) You can create a reference to this and use it.

send: function() {
  let self = this
  axios.post(this.api + "orders", this.order).then(function(response) {
    self.message = "Your payment was successful"
  }
}

2) An arrow function will enable you to use this which will point to your Vue instance.

send: function() {
  axios.post(this.api + "orders", this.order).then(response => {
    this.message = "Your payment was successful"
  }
}

3) Use bind to assign an object to this which will be the current Vue instance in your case.

send: function() {
  axios.post(this.api + "orders", this.order).then(function(response) {
    this.message = "Your payment was successful"
  }.bind(this))
}