Value of 'this' for VueJs 2 in promise [duplicate] - vuejs2

This question already has answers here:
Scope of this in javascript function
(3 answers)
Closed 4 years ago.
Learning Vue for a simple admin panel project. I cannot access my Vue instance local variables in a method's returned promise using 'this'.
I am only able to access it by directly calling the vue instance itself. is this the correct way to handle this.
var config = {
apiKey: "",
authDomain: "",
databaseURL: "",
projectId: "",
storageBucket: "",
messagingSenderId: ""
};
firebase.initializeApp(config);
const db = firebase.firestore()
const auth = firebase.auth()
const currentUser = auth.currentUser
// firebase collections
const categoryCollection = db.collection('predefinedCategories')
var example2 = new Vue({
el: '#components-demo',
data: {
message: 'Message',
},
methods: {
initMessage: function () {
categoryCollection.get().then(
function(querySnapshot) {
message = querySnapshot.forEach((doc) => {
console.log(doc.id, " => ", doc.data(), doc.data().name);
console.log(example2.$data.message);
console.log(this.data.message);
});
});
},
checkMessage: function () {
console.log(this.message);
}
}
})
console.log(this.data.message) should give me 'Message'. Instead it gives me undefined.

The this in the .then() callback no longer refers to your Vue instance, because it is scoped in a function. You can of course proxy it to another variable, but since you are using ES6 already, you can use arrow function. The major advantage of arrow function is that it preserves the lexical this.
Also, if you want to refer to the app's message data, it should be referenced as this.message and not this.data.message.
initMessage: function () {
categoryCollection.get().then(querySnapshot => {
message = querySnapshot.forEach(doc => {
console.log(this.message);
});
});
},

Related

Vue 3 Pinia - Fetched value from API in Pinia store can not be assigned as constant, always reactive [duplicate]

This question already has answers here:
What is the most efficient way to deep clone an object in JavaScript?
(67 answers)
Closed 10 days ago.
I have a vue3 frontend set up with pinia as store, like this:
export const useShopStore = defineStore(
"shop",
() => {
const state = reactive({
data: {
fruits: [],
owners: [],
},
});
let data_copy = {
fruits: [],
owners: [],
};
async function fetchData() {
const URL = `${API_URL}/shops`;
await axios
.get(URL)
.then((res) => {
state.data = { ...res.data };
data_copy = { ...res.data };
})
.catch((err) => {
if (err.response) {
console.log(err.response);
}
});
}
return { data, data_copy, fetchData };
},
{
persist: true,
}
);
PROBLEM:
NO matter what i tried, the variable data_copy is always a reactive copy of data.state.
GOAL:
What i want to achieve is that i fetch the data and assign it
a.) to the reactive variable state.data as well as
b.) to the NON-reactive variable data_copy
REASON:
In the template, i have a checkbox group and a computed property to compare the arrays in state.data with the data object that was originally fetch, data_copy. If the state.data object changed, a button "Update changes" is enabled.
Meaning, after fetching the data from the API and assigning the result to data_copy, data_copy should never change.
I also tried Object.assign({}, res.data) instead of the deconstruction syntax, that also did not work.
QUESTION:
How can i get a truly constant copy from the fetchData result independent of state.data?
Thanks for you help!
Finally, i tried the following which works:
async function fetchData() {
const URL = `${API_URL}/shops`;
await axios
.get(URL)
.then((res) => {
state.data = JSON.parse(JSON.stringify(res.data));
data_copy = JSON.parse(JSON.stringify(res.data));
})
.catch((err) => {
if (err.response) {
console.log(err.response);
}
});
}
I found the answer here.

How to run method inside a response function using Vue.js

I'm trying to run a method when I get success response from API, but the method dont run. I made a quick example here to show.
The test() function should be executed after i get the response, since its calling another API endpoint. Here is the vue.js code.
var app = new Vue({
el: "#contents",
data: {
id: null,
details: [],
},
methods: {
fetchProductDetails: function(){
let vue = this;
axios.post("/api/get-details", {
id : id
})
.then(function (response) {
vue.details = response.data;
this.test();
})
.catch(function (error) {});
},
test: function () {
console.log(app.details);
}
},
mounted: function(){
this.fetchProductDetails();
},
});
You should run vue.test() instead of this.test(), just like you use vue.details = response.data instead of this.details = response.data.
When using an unnamed function in .then(), this no longer refers to your vue application, but to the unnamed function. You could use ES6 arrow function syntax in order to avoid having to set this to a specific variable, as arrow functions use their parent's scope for this instead of setting this to refer to themselves:
axios.post("/api/get-details", { id: this.id })
.then(response => {
this.details = response.data;
this.test();
})
.catch(error => { console.log(error)});
Arrow functions (and ES6 in general) are not supported by IE11 however. so you'd need to use Babel to compile it back to something ES5 JavaScript if you need to support older browsers.

Vuex - Passing a state's object into another state's object

In Vuex I'm trying to pass a state's object (a string in this case), into another state's object, but it is returning undefined.
state: {
notifications: [
{ key: "success",
notification: "Awesome " + this.theName + "! Success.",
redirectPath: "/home"
},
{ key: "error",
notification: "Oh no " + this.theName + "... Error.",
redirectPath: "/error"
}
],
theName: 'Ricky Bobby' // this would normally come from a mutation method - see below
}
The example above the theName is hard-coded just for testing but its value is coming from a mutation method. I know it is coming in into the store's state, because I am able to console log it. But the string interpolation inside the notifications object is not working. How can I pass that incoming value into the notifications.notification value?
I don't know if this helps, but here is the mutation example:
mutations: {
loginSuccess(state, payload){
state.theName = payload.uName;
}
}
There're two issues with your code. Firstly, this doesn't work the way you're trying to make it to do. In your question this inside each notification doesn't refer to the state or any other part of your code. Its value is the global window object or undefined, depends on whether you are in strict mode:
const object = {
propName: this,
};
console.log(object.propName);
Secondly, you code is asynchronous, so theName would change from time to time, but you never actually redefine message strings in your notifications. And they won't be 'recalculated' by itself:
let surname = 'Whyte';
const object = {
fullName: 'Pepe ' + surname,
};
console.log(object.fullName);
setTimeout(() => {
surname = 'White';
console.log(object.fullName);
console.log('the value of \'surname\' variable is ' + surname + ' though.');
}, 2000);
What you can do in your case is to define notification as a function:
notification(name) { return "Awesome " + name + "! Success."}
Then write a getter for notifications and pass a name to the function.
Or as an alternative you can refer to the object itself inside the function. Like this:
let surname = 'Whyte';
const object = {
person: {
firstName: 'Pepe ',
fullName: () => {
return object.person.firstName + ' ' + surname;
},
}
};
console.log(object.person.fullName());
setTimeout(() => {
object.person.firstName = 'Keke';
console.log(object.person.fullName());
}, 1000);
UPD: I've made another example for you. It's hard to tell how exactly you are going to call this notifications, but here are two options you can access them the way you want (jsfiddle):
const store = new Vuex.Store({
state: {
theName: 'Ricky Bobby',
// accessing `theName` prop inside state (probably won't be possible in the real project or very inconvinient due to modularity)
successNotificationInState: () => `Awesome ${store.state.theName}! Success.`,
},
// accessing the same prop with getter
getters: {
successNotification: (state) => `Awesome ${state.theName}! Success.`,
},
mutations: {
loginSuccess(state, payload) {
state.theName = payload.uName;
},
},
actions: { // let's emulate a login
login({
commit
}) {
return new Promise(fullfil => {
setTimeout(function() {
console.log('logging in')
const response = {
uName: 'Keke',
email: 'keke#gmail.com',
}
fullfil(response);
commit('loginSuccess', response);
}, 2000);
});
},
},
});
const app = new Vue({
el: "#app",
store,
data: {
msgGetter: '',
msgState: '',
},
computed: {},
methods: {
login() {
this.$store.dispatch('login').then((response) => {
console.log(response);
console.log(this.$store);
this.msgGetter = this.$store.getters.successNotification;
this.msgState = this.$store.state.successNotificationInState();
});
},
},
mounted() {
this.login();
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/3.0.1/vuex.js"></script>
<script src="https://unpkg.com/vue"></script>
<div id="app">
<p>Message from state: {{msgState}}</p>
<p>Message from getter: {{msgGetter}}</p>
</div>

Vue.js Creating Reusable Functions

My code is horrible. I'm new on this Vue.js. As you can see I have two AJAX call that needs to become a function. How I'm going to do it. I can't use for example
var app = this
this.allAdmissions('http://localhost/school/api/admissions', app.admission)
and then in my methods I will just
allAdmissions: _.debounce( function(url, value){
axios.get(url)
.then( function(response ){
value = response.data.admissions
})
.catch( function(error){
console.log(error)
})
}),
It doesn't work. I need to create a function to combine this two.
var app = new Vue({
el: '#app',
data: {
value: '',
admissions: [],
schoolyear: []
},
created: function(){
this.allAdmissions('http://localhost/school/api/admissions')
this.allSchoolYear('http://localhost/school/api/schoolyear')
},
methods: {
allAdmissions: _.debounce( function(url){
var app = this
axios.get(url)
.then( function(response ){
app.admissions = response.data.admissions
})
.catch( function(error){
console.log(error)
})
}),
allSchoolYear: _.debounce( function(url){
var app = this
axios.get(url)
.then( function(response ){
app.schoolyear = response.data.schoolYear
})
.catch( function(error){
console.log(error)
})
})
}
})
I see that you are using promises for the ajax request which is a great step forward. I would suggest creating a separate js object with a single entrypoint (public) method to consolidate the logic of making both requests. this single object would return a composed promise that is resolved when both requests are fulfilled. take a look at Promise.All for this. then you would call your custom method from the ready hook inside the vue instance. https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise/all.
what this will get you is code that is easier to reason about and debug for typos or any other flow mistake you may have made.
Also another suggestion is that you use arrow functions for your callbacks like so.
.then((response) => {
this.admissions = response.data.admissions
})
what this will allow you to do is skip the
var app = this
binding so that 'this' retains the reference to your current vue instance.

Axios can't set data

Here's my data:
data: function(){
return {
contas: [{id: 3,
nome: "Conta de telefone",
pago: false,
valor: 55.99,
vencimento: "22/08/2016"}] //debug test value
};
},
And here's my get request:
beforeMount() {
axios.get('http://127.0.0.1/api/bills')
.then(function (response) {
console.log("before: " + this.contas);
this.contas = response.data;
console.log("after: " + this.contas);
});
},
The problem is I can't access this.contas from within axios.get(). I've tried Vue.set(this, 'contas', response.data); and window.listaPagarComponent.contas = response.data; without success.
My console shows:
before: undefined
after: [object Object],[object Object],[object Object],[object Object],[object Object],[object Object]
But Vue Devtools shows only:
contas: Array[1]
0: Object
id: 3
nome: "Conta de telefone"
pago: false
valor: 55.99
vencimento: "22/08/2016"
Here's my full code.
In option functions like data and created, vue binds this to the view-model instance for us, so we can use this.contas, but in the function inside then, this is not bound.
So you need to preserve the view-model like (created means the component's data structure is assembled, which is enough here, mounted will delay the operation more):
created() {
var self = this;
axios.get('http://127.0.0.1/api/bills')
.then(function (response) {
self.contas = response.data;
});
}
Or you can use arrow function in ES6 standard if you only aim to support modern browsers(or using a transpiler like babel), like:
created() {
axios.get('http://127.0.0.1/api/bills')
.then((response) => {
this.contas = response.data;
});
}
this inside arrow functions are bound according to lexical context, which means the this in the above snippet is the same as the one in created, which is what we want.
To be able to access this.contas inside axios.get() do you need binding "this" to keep variable usage scoped:
mounted() {
axios.get('http://127.0.0.1/api/bills')
.then(function (response) {
console.log("before: " + this.contas);
this.contas = response.data;
console.log("after: " + this.contas);
}.bind(this));
}