Accessing VUE JS's data from Axios - javascript

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?

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))
}

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.

Related

Vuex Getter Undefined

I am new to Vue.js and experiencing an issue with Vuex modules and Axios. I have a "post" component that retrieves a slug from the router and fetches data with Axios which is then retrieved with Vuex Getters.
I am able to retrieve data successfully but then I still see this error on my DevTools, "TypeError: Cannot read property 'name' of undefined"
Due to this error I am not able to pass this.post.name to Vue-Meta.
Codes
Post.vue
computed: {
...mapGetters(["post"]),
},
mounted() {
const slug = this.$route.params.slug;
this.fetchPost({ slug: slug });
},
methods: {
...mapActions(["fetchPost"]),
/store/modules/post.js
const state = {
post: [],
};
const getters = {
post: (state) => {
return post;
}
};
const actions = {
async fetchPost({ commit }, arg) {
try {
await axios.get("/post/" + arg.slug).then((response) => {
commit("setPost", response.data);
});
} catch (error) {
console.log(error);
}
},
};
const mutations = {
setPost: (state, post) => (state.post = post),
};
export default {
state,
getters,
actions,
mutations,
};
Your getter is utterly wrong: a state getter is supposed to be a function that takes in the entire state as a param and retrieves whatever you're interested in from it. Your version...
const getters = {
post: (state) => {
return post;
}
};
...takes in the state as a param but doesn't use it. Instead, it returns a variable (post) which has not been defined in that context.
Which will always return undefined, regardless of current value of state.post.
And, as you already know, JavaScript can't access property 'name' of undefined.
To get the current value of state.post, use:
const getters = {
post: state => state.post
}
Or
const getters = {
post: (state) => { return state.post; }
}
... if you fancy brackets.
Also, out of principle, I suggest initializing your post with an empty object {} instead of an empty array [].
Changing variable types as few times as possible is a very good coding habit, providing huge benefits in the long run.
Edit (after [mcve])
You have a bigger problem: the import from your axios plugin returns undefined. So you can't call get on it. Because you wrapped that call into a try/catch block, you don't get to see the error but the endpoint is never called.
I don't know where you picked that plugin syntax from but it's clearly not exporting axios. Replacing the import with import axios from 'axios' works as expected.
Another advice would be to namespace your store module. That's going to become useful when you'll have more than one module and you'll want to specifically reference a particular mutation/action on a specific module. You'll need to slightly change mapActions and mapGetters at that point.
See it working here.

How to access a Vue plugin from another plugins (using Vue.prototype)?

I'm trying to write a Vue plugin that's a simple abstraction to manage auth state across my app. This will need to access other Vue plugins, namely vuex, vue-router and vue-apollo (at the moment).
I tried extending Vue.prototype but when I try to access the plugin's properties how I would normally - eg. this.$apollo - I get the scope of the object, and therefore an undefined error. I also tried adding vm = this and using vm.$apollo, but this only moves the scope out further, but not to the Vue object - I guess this is because there is no instance of the Vue object yet?
export const VueAuth = {
install (Vue, _opts) {
Vue.prototype.$auth = {
test () {
console.log(this.$apollo)
}
}
}
}
(The other plugins are imported and added via. Vue.use() in the main app.js)
Alternatively, I tried...
// ...
install (Vue, { router, store, apollo })
// ...
but as a novice with js, I'm not sure how this works in terms of passing a copy of the passed objects, or if it will mutate the originals/pass by ref. And it's also very explicit and means more overhead if my plugin is to reach out to more plugins further down the line.
Can anyone advise on a clean, manageable way to do this? Do I have to instead alter an instance of Vue instead of the prototype?
In the plugin install function, you do not have access to the Vue instance (this), but you can access other plugins via the prototype. For example:
main.js:
Vue.use(Apollo)
Vue.use(VueAuth) // must be installed after vue-apollo
plugin.js:
export const VueAuth = {
install (Vue) {
Vue.prototype.$auth = {
test () {
console.log(Vue.prototype.$apollo)
}
}
}
}
I found a simple solution for this issue:
In plugin installer you need to add value to not just prototype, but Vue itself to be able to use it globally.
There is a code example:
Installer:
import apiService from "../services/ApiService";
// Service contains 'post' method
export default {
install(Vue) {
Vue.prototype.$api = apiService;
Vue.api = apiService;
}
};
Usage in other plugin:
import Vue from "vue";
...
const response = await Vue.api.post({
url: "/login",
payload: { email, password }
});
Usage in component:
const response = await this.$api.post({
url: "/login",
payload: { email, password }
});
I'm not sure if that's a good solution, but that made my scenario work perfectly.
So, I got around this by converting my property from a plain ol' object into a closure that returns an object, and this seems to have resolved my this scoping issue.
Honestly, I've jumped into Vue with minimal JS-specific knowledge and I don't fully understand how functions and the likes are scoped (and I'm not sure I want to look under that rock just yet......).
export const VueAuth = {
install (Vue, opts) {
Vue.prototype.$auth = function () {
let apollo = this.$apolloProvider.defaultClient
let router = this.$router
return {
logIn: function (email, password) {
apollo.mutate({
mutation: LOGIN_MUTATION,
variables: {
username: email,
password: password,
},
}).then((result) => {
// Result
console.log(result)
localStorage.setItem('token', result.data.login.access_token)
router.go(router.currentRoute.path)
}).catch((error) => {
// Error
console.error('Error!')
console.error(error)
})
},
logOut: function () {
localStorage.removeItem('token')
localStorage.removeItem('refresh-token')
router.go()
console.log('Logged out')
},
}
}
It's a rudimental implementation at the moment, but it'll do for testing.

Uncaught (in promise) TypeError: Cannot set property 'playerName' of undefined at eval

I'm trying to assign response.data.Response.displayName from my GET request to my playerName property, however, I am getting an error "Uncaught (in promise) TypeError: Cannot set property 'playerName' of undefined at eval". I am successfully console logging response.data.Reponse.displayName so there is a displayName in it.
Why am I getting this error?
export default {
data: function() {
return {
playerName: ''
}
},
methods: {
},
mounted() {
axios.get('/User/GetBungieNetUserById/19964531/')
.then(function(response) {
this.playerName = response.data.Response.displayName
console.log(response.data.Response.displayName)
});
}
}
Other comments and answers are correct - using an arrow/lambda function instead of just function will work. But there's a nuance as to why.
Javascript's concept of this is well defined but not always what you'd expect from other languages. this can change within one scope block when you're executing from sub-functions of things like callbacks. In your case, the function in the then no longer understands this as the same as if you were running the same code directly inside mounted().
You can bind functions, however, to (among other purposes) have a specific this attached that can't be changed. Arrow functions do this implicitly, and bind this to what this is in the context the arrow function is created. Therefore, this code:
axios.get('/User/GetBungieNetUserById/19964531/')
.then((response) => {
this.playerName = response.data.Response.displayName
console.log(response.data.Response.displayName)
});
understands this properly. It is (roughly!) equivalent to the following:
axios.get('/User/GetBungieNetUserById/19964531/')
.then((function(response) {
this.playerName = response.data.Response.displayName
console.log(response.data.Response.displayName)
}).bind(this));
Use lambda function ( Arrow function ) to reach the code
export default {
data: function() {
return {
playerName: ''
}
},
methods: {
},
mounted() {
axios.get('/User/GetBungieNetUserById/19964531/')
.then((response) => {
self.playerName = response.data.Response.displayName
console.log(response.data.Response.displayName)
});
}
}

Lodash's _.debounce() not working in Vue.js

I am trying to run a method called query() when a component property called q in Vue.js is modified.
This fails because this.query() is undefined. This is referring to my component's instance but somehow does not contain the methods.
Here's the relevant code part where I'm trying to watch the component property q and run the query() function:
methods: {
async query() {
const url = `https://example.com`;
const results = await axios({
url,
method: 'GET',
});
this.results = results.items;
},
debouncedQuery: _.debounce(() => { this.query(); }, 300),
},
watch: {
q() {
this.debouncedQuery();
},
},
Error:
TypeError: _this2.query is not a function
If I write the debounce() call as below, the TypeError: expected a function error appears even earlier, at the page load.
debouncedQuery: _.debounce(this.query, 300),
The issue comes from the lexical scope of the arrow function you define within _.debounce. this is bound to the object you are defining it in, not the instantiated Vue instance.
If you switch out your arrow function for a regular function the scope is bound correctly:
methods: {
// ...
debouncedQuery: _.debounce(function () { this.query(); }, 300)
}
We can do it by plain JS (ES6) with few lines of code:
function update() {
if(typeof window.LIT !== 'undefined') {
clearTimeout(window.LIT);
}
window.LIT = setTimeout(() => {
// do something...
}, 1000);
}
As answered in another post This is undefined in Vue, using debounce method the best way to add debouncing IMO is to create the method normally in methods as eg:
setHover() {
if (this.hoverStatus === 'entered') {
this.hoverStatus = 'active'
}
},
But then replace it in your created block eg:
created() {
this.setHover = debounce(this.setHover, 250)
},

Vue this inside data() factory function

Can I rely on this used inside data factory function as it was current component object instance? I couldn't find in docs what this is in data().
data() {
return {
results: [],
apiResource: this.$resource('url...'), // <-- this used here
loading: true,
}
},
Simple test shows that this is VueComponent instance here, but the question is if the framework allows using it this way.
Yes, you can rely on this in the data factory function pointing to the component, depending on how you define the function. It's the primary way of initializing local data with values from properties, for example.
props:["value"],
data(){
return {
localValue: this.value
}
}
If, however, you defined your data function with an arrow function, this will not be the component.
props:["value"],
data: () => {
// 'this' is NOT the component
return {
localValue: this.value // results in undefined
}
}
I think no
Perhaps you need
data() {
return {
results: [],
set apiResource(v){},
get apiResource()( return this.$resource('url...')), // <-- this used here
loading: true,
}
},

Categories

Resources