Cannot access this whilst debouncing search request in Vue with Lodash? - vue.js

I have a simple search input:
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js" type="text/javascript"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.10/vue.js" type="text/javascript"></script>
<div id="app">
<input type="text" v-model="search">
</div>
When you enter a value it will query a remote API to fetch and then display the data:
...
new Vue({
el: '#app',
data: {
people: []
search: ''
},
watch: {
search() {
// Rate limit
this.debouncedSearch()
}
},
methods: {
debouncedSearch: _.debounce(() => {
console.log(this)
// self = this
// io.socket.put('/search', this.search, (people, jwres) => {
// self.people = people
// })
}, 500)
},
created(){
this.people = locals.people
}
})
The problem here is that console.log(this) returns undefined.
I have used this in another application and it works so battling to understand why not here.
Is there something that I have done wrong there, seems to be correct but no matter what I try I cannot access the scope of the Vue application in that debouncedSearch method?

To solve the issue just replace the arrow function with function() {}
Replace this:
methods: {
debouncedSearch: _.debounce(() => {
console.log(this) // undefined
}, 500)
},
With This:
methods: {
debouncedSearch: _.debounce(function () {
console.log(this) // not undefined
}, 500)
},
Hope to help others took me a lot of time to figure it out.

Pretty sure your problem is the use of the this-preserving function style. You need a way to refer to the Vue object you're creating (it's not this at the point your debounce function is defined). One way would be to do
const vm = new Vue({
el: '#app',
...
methods: {
debouncedSearch: _.debounce(() => {
console.log(vm)
// io.socket.put('/search', vm.search, (people, jwres) => {
// vm.people = response
// })
}, 500)
},

Related

Vue2 create component based on data

I want to create a component based on ajax api response or data which include:
template
data
methods - there may be several methods
Remark: response or data is dynamic and it is not saved in file.
I have tried to generate and return result like :
<script>
Vue.component('test-component14', {
template: '<div><input type="button" v-on:click="changeName" value="Click me 14" /><h1>{{msg}}</h1></div>',
data: function () {
return {
msg: "Test Componet 14 "
}
},
methods: {
changeName: function () {
this.msg = "mouse clicked 14";
},
}
});
</script>
and do compile above code :
axios.get("/api/GetResult")
.then(response => {
comp1 = response.data;
const compiled = Vue.compile(comp1);
Vue.component('result-component', compiled);
})
.catch(error => console.log(error))
I got error on Vue.compile(comp1) -
Templates should only be responsible for mapping the state to the UI. Avoid placing tags with side-effects in your templates, such as
<script>, as they will not be parsed.
Thanks in advance
Your Api should return a JSON with every property required by a Vue component (name, data, template, methods), note that methods needs to be converted into an actual js function (check docs about that)
Vue.config.productionTip = false;
Vue.config.devtools = false;
new Vue({
el: '#app',
data() {
return {
apiComponent: { template: '<div>Loading!</div>' }
};
},
methods: {
loadApiComponent() {
setTimeout(() => {
this.buildApiComponent(JSON.parse('{"name":"test-component14","template":"<div><input type=\\\"button\\\" v-on:click=\\\"changeName\\\" value=\\\"Click me 14\\\" /><h1>{{msg}}</h1></div>","data":{"msg":"Test Componet 14 "},"methods":[{"name":"changeName","body":"{this.msg = \\\"mouse clicked 14\\\";}"}]}'));
}, 2000);
},
buildApiComponent(compObject) {
const {
name,
template,
data,
methods
} = compObject;
const compiledTemplate = Vue.compile(template);
this.apiComponent = {
...compiledTemplate,
name,
data() {
return { ...data
}
},
methods: methods.reduce((c, n) => {
c[n.name] = new Function(n.body);
return c;
}, {})
};
}
},
mounted() {
this.loadApiComponent();
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<component :is="apiComponent" />
</div>

VueJS - Cannot read property 'status' of undefined

Although the output is correct. I have this error: Cannot read property 'status' of undefined. I suspect it is because listing is added after ajax call. How do I improve this?
<template v-for="status in showStatus">
<div class="col-sm-1">{{status}}</div>
</template>
<script>
var app = new Vue({
el: "#app",
data: {
listing: []
},
created: function(){
axios.get('/getListing')
.then(function (response) {
app.listing = response.data;
})
.catch(function (error) {
console.log(error);
});
},
computed: {
showStatus(){
return this.listing[0].status;
}
}
});
</script>
As you've guessed, this is because listing starts as an empty array which is populated at a later time.
Your computed property doesn't do any checks for this and this.listing[0] will be undefined early on.
You just need to add a check to your computed poperty
computed: {
showStatus () {
return this.listing[0]?.status || []
}
}
See Optional chaining (?.) if you haven't seen that syntax before.
An alternative would be to initialise listing with some safe default data
data: () => ({
listing: [{ status: [] }]
})

Possible to compute/watch property from 'outside' Vue?

I am trying to wrap my head around the reactivity in Vue and how/if I can use it in regard to nested properties in objects from my own and other libraries that are not declared within the Vue components.
Here is one JS fiddle trying to use compute:
http://jsfiddle.net/73r9bk2t/1/
<div id="app">
{{item}}
</div>
var someExternalObject = { thing: 'some value' }
var vm = new Vue({
el: '#app',
computed: {
item() {
return someExternalObject.thing;
}
},
})
setTimeout(() => {
someExternalObject.thing = 'new value';
console.log(someExternalObject.thing);
}, 1000)
Here is another trying to use $watch
http://jsfiddle.net/73r9bk2t/2/
<div id="app">
{{item}}
</div>
someExternalObject = { thing: 'some initial text' }
var vm = new Vue({
el: '#app',
computed: {
item() {
return someExternalObject.thing;
}
},
created()
{
// Give the external object a scoped reference
this.someExternalObject = someExternalObject;
this.$watch('someExternalObject.thing', function (newVal, oldVal)
{
console.log("Watched:", newVal, oldVal);
}, { deep: true, immediate: true });
}
})
setTimeout(() => {
someExternalObject.thing = 'some updated text';
console.log(someExternalObject.thing);
}, 1000)
But nothing seems to work (text output is not updated). I am starting to wonder if I am trying to do something that I shouldn't do.
If you're looking to integrate an external library into Vue and make it reactive then you should consider using Vue.observable. It will let you create a reactive object outside of a Vue instance.
const state = Vue.observable({ count: 0 })
const Demo = {
render(h) {
return h('button', {
on: { click: () => { state.count++ }}
}, `count is: ${state.count}`)
}
}
https://v2.vuejs.org/v2/api/#Vue-observable
Hope this helps!
In Vue 3 you can use ref() or reactive():
import { reactive } from 'vue';
const state = reactive({ count: 0 });
Relevant links:
Reactivity Fundamentals (vuejs.org)
ref vs reactive in Vue 3? (stackoverflow.com)

VueJs Nested props coming through undefined

I am trying to access an array which is part of a prop (event) passed into a component, but when in created() or mounted() the array part of the event prop (the rest is fine) comes through as undefined.
As can be seen below, when I inspect the props in the vue chrome plugin, the registration_fields are there.
I can add a watcher to the event prop and can access the registration_fields that way, but this seems very awkward to have to do this to access already passed in data.
This is from the Chrome vue inspector:
event:Object
address1_field:"Some Address 1"
address2_field:"Some Address 2"
approved:true
registration_fields:Array[1]
This is what part of my vue file looks like:
export default {
props: ['event'],
data() {
return {
regFields: []
}
},
created() {
this.regFields = this.event.registration_fields // Undefined here!
},
watch: {
event() {
this.regFields = this.event.registration_fields //Can access it here
});
}
}
}
I am using Vue 2.4.4
This is how the component is called:
<template>
<tickets v-if="event" :event="event"></tickets>
</template>
<script>
import tickets from './main_booking/tickets.vue'
export default {
created() {
var self = this;
this.$http.get('events/123').then(response => {
self.event = response.data
}).catch(e => {
alert('Error here!');
})
},
data: function () {
return {event: {}}
},
components: {
tickets: tickets
}
}
</script>
Thank you
It actually works fine without the watcher.
new Vue({
el: '#app',
data: {
event: undefined
},
components: {
subC: {
props: ['event'],
data() {
return {
regFields: []
}
},
created() {
this.regFields = this.event.registration_fields // Undefined here!
}
}
},
mounted() {
setTimeout(() => {
this.event = {
registration_fields: [1, 3]
};
}, 800);
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="app">
<sub-c v-if="event" :event="event" inline-template>
<div>
{{regFields}}
</div>
</sub-c>
</div>
If, as Belmin Bedak suggests in the comment below, event is populated asynchronously, it comes in as undefined because it's undefined. In that case, you need a watcher, or, somewhat more elegantly, use a computed:
new Vue({
el: '#app',
data: {
event: {}
},
components: {
subC: {
props: ['event'],
computed: {
regFields() {
return this.event.registration_fields;
}
}
}
},
// delay proper population
mounted() {
setTimeout(() => { this.event = {registration_fields: [1,2,3]}; }, 800);
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="app">
<sub-c :event="event" inline-template>
<div>
{{regFields}}
</div>
</sub-c>
</div>

Where I should place handler of emit?

I have child component and want to pass some data to it's parent.
My child component looks like:
// <button #click="sendClick($event)">Send</button>
// ...
data: function (){
return {
mycode: ""
}
},
methods: {
sendClick(e)
{
bus.$emit('change', this.mycode);
}
}
My parent component looks:
var app = new Vue({
el: '#app',
data: {
currentView: 'past-form',
mycode: ''
},
methods:
{
changeView()
{
this.currentView = 'past-form'
console.log(this.mycode);
},
},
created()
{
bus.$on('change', function(mycode){
this.mycode = mycode;
});
}
})
I haven't found a better place for placing bus.$on (bus is declared globally) than in created(), but the docs state that created() is for stuff that should be initialized after the page is loaded. The created() block works; I checked it by placing in it console.log(this.mycode), but should I move emit handler somewhere else?
It's look like my code does not execute mycode: '', because console.log(this.mycode); does not print anything.
As I mentioned in the comment, if your component is a direct child of your Vue, then there is no need for a bus.
That said, the created handler is fine for adding your bus event handler.
I expect the issue you have is a this issue. Try changing your handler to
bus.$on('change', mycode => this.mycode = mycode)
See How to access the correct this inside a callback?
Here is an example.
console.clear()
const bus = new Vue()
Vue.component("child", {
template: `<button #click="sendClick($event)">Send</button>`,
data: function() {
return {
mycode: "something"
}
},
methods: {
sendClick(e) {
bus.$emit('change', this.mycode);
}
}
})
var app = new Vue({
el: '#app',
data: {
currentView: 'past-form',
mycode: ''
},
methods: {
changeView() {
this.currentView = 'past-form'
console.log(this.mycode);
},
},
created() {
bus.$on('change', mycode => {
this.mycode = mycode
this.changeView()
})
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.js"></script>
<div id="app">
<child></child>
Parent mycode: {{mycode}}
</div>