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

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

Related

How to change the context of this inside object

I want to access a function which is outside my base object, but if we console.log(this) inside loadAll function then it will give the context inside object. If I console.log(this) any normal function i.e without action or getter/setters it will give context of whole class which I want to achieve
base = observable({
isDeleting: false,
registry: observable.map(),
get all() {
return this.registry.values();
},
loadAll:action.bound(function () {
console.log(this);
this.request()
}),
});
request = () => {
console.log('hello');
}
In The above example this will throw an error because it can't access the request function but I wan to find a way to access it.
base = observable({
isDeleting: false,
registry: observable.map(),
get all() {
return this.registry.values();
},
loadAll:action.bound(function () {
console.log(this);
this.request()
}),
request: () => {
console.log(this) //This will give context of all the function outside object that is what I want in above example.
console.log('hello');
},
});

TypeError: Cannot read properties of undefined (reading 'location') in res.redirect

I have the following function to retrieve an object from a database and extract an URL:
async redirect(id: string, redirectFunction: Function) {
if (!IdExists(id)) {
throw new Error(`ID ${id} does not exist.`);
}
const redirectLocation: string = await prisma.url.findUnique({
where: { uniqueId: id },
select: { url: true },
}).then((data) => {
return data?.url!;
});
redirectFunction('http://' + redirectLocation);
}
The function is called in the following segment of code:
app.get('/:id', async (req, res) => {
try {
redirectController.redirect(req.params.id, res.redirect);
} catch (error) {
console.error(error);
}
});
However, I get the TypeError: Cannot read properties of undefined (reading 'location'), I see that the error is related to the res.redirect method. However, when I replace it by console.log for debugging, the URL is showed properly. What may be causing this error?
This line of code:
redirectController.redirect(req.params.id, res.redirect);
Passes res.redirect (a function reference) as the second argument, but all that is passed is just the function so the res gets lost when you later try to call it. That causes the method to have a wrong this value when it executes and lots of things go wrong.
You can fix that several different ways. Once such way is with .bind():
redirectController.redirect(req.params.id, res.redirect.bind(res));
.bind() creates a small stub function that remembers the value of res so that when the stub function is called, it will be called with the right res reference and thus the this value inside the function will be correct.
Another way to solve it is to create your own little stub function:
redirectController.redirect(req.params.id, (...args) => {
res.redirect(...args);
});
When it calls your stub function, you call res.redirect() properly and pass it whatever arguments the controller called your stub function with.
As a small demonstration, you can see this effect here:
const obj = {
greeting: "Hello",
talk: function() {
if (this && this.greeting) {
console.log(`this.greeting is "${this.greeting}"`);
} else {
console.log("value of this is wrong");
}
}
}
console.log("calling as obj.talk()");
obj.talk(); // works
console.log("-------------------------");
// function we pass a method to and then call that method
function callTalk(fn) {
fn();
}
console.log("calling by passing method to another function");
callTalk(obj.talk); // doesn't work
// call it using .bind()
console.log("-------------------------");
console.log("calling using .bind()");
callTalk(obj.talk.bind(obj)); // works

The this is undefined if used inside a debounce function

For some reason the this is undefined if I'm using a debounce function. I have tried to bind it but still it is undefined. I can't understand what is happening here..
For example here this is working and returns
VueComponent {_uid: 6, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: VueComponent, …}
<template>
<at-ta #at="fetchMembers">
<textarea></textarea>
</at-ta>
</template>
fetchMembers(at)
{
console.log(this) // <-- VueComponent { ...
},
But when I move it to a debounce function it isn't working anymore but is
this is undefined
fetchMembers: debounce(at => {
axios.get(`/api/users?name=${at}`).then(({data}) => {
this.members = data // <-- this is undefined
})
}, 500)
Don't bind the debounced function, but do bind the axios promise callback.
methods: {
fetchMembers: debounce(function (at) {
axios.get(`/api/users?name=${at}`).then(({data}) => {
this.members = data
})
}, 500)
}
It didn't work for you because you used a fat arrow function for the debounced function where this is undefined at that scope.
Keep in mind that you're only creating one debounced function that will be shared across all instances of your component. If this is an issue, you can instead wrap fetchMembers with a debounced function in the created hook:
created() {
this.fetchMembers = _.debounce(this.fetchMembers, 500)
},
methods: {
fetchMembers() {
axios.get(`/api/users?name=${at}`).then(({data}) => {
this.members = data
})
}
},

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

Accessing VUE JS's data from Axios

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.

Categories

Resources