How to access Vue Instance from within an argument function? - vue.js

So based off this thread I implemented this snippet:
methods: {
checkSearchString: _.debounce( string => {
console.log("watcher firing for search!");
console.log(this.searchInput);
this.$emit("search", this.searchInput);
}, 2000)
},
watch: {
searchInput : "checkSearchString"
}
but as comments on the accepted answer pointed out, "this" does not points to the vue instance, so I can't access it. How could I access the vue instance from within the function? or how could I solve this better?
the main goal here is to use _.debounce and a watcher to fire the search when the user stops typing, and achieve that in a clean way.
Edit: Thanks for pointing the use of the arrow function as the problem of context here, the users on the other thread did point to this being the problem but I didn't get why

you using arrow function which losing context.
do it with normal anonymous function
watch: {
searchInput : "checkSearchString"
}
methods: {
checkSearchString: _.debounce( function(value) {
console.log("watcher firing for search!");
console.log(value);
this.$emit("search", value);
}, 2000)
},

Related

How to access "this" from Uppy's callback event

I'm using the Uppy Vue component library, and following the docs, I've initialized Uppy by adding it as a computed property.
computed: {
uppy: () => new Uppy({
logger: Uppy.debugLogger
}).use(AwsS3Multipart, {
limit: 4,
companionUrl: '/',
}).on('complete', (result) => {
this.testing = 'success';
console.log('successful files:', result.successful);
console.log('failed files:', result.failed);
}),
}
I'm trying to update my Vue component's data now by using Uppy's complete event, but "this" is not defined. I'm not quite sure how to access "this" from here.
Any idea how to go about doing this?
Update
After posting this, I found a solution that works. I'm hesitant with this solution though as it seemed too easy.
If no one provides a better solution, I'll add this as the answer.
// Uppy Instance
uppy: function() {
return new Uppy({
logger: Uppy.debugLogger
}).use(AwsS3Multipart, {
limit: 4,
companionUrl: '/',
}).on('complete', (result) => {
this.testing = 'success';
console.log('successful files:', result.successful);
console.log('failed files:', result.failed);
})
},
By following the Uppy docs and instantiating the Uppy instance with an arrow function, this no longer seems to refer to the Vue. This makes it so that accessing this.method(), or this.variable, etc. no longer works.
My solution was to change the Uppy instantiation from an arrow function to a regular function. I believe this causes this to refer to the global instance, but I don't have a solid understanding of this, so take what I say with a grain of salt.
I changed this:
computed: {
uppy: () => new Uppy()
}
To this:
computed: {
uppy: function() { return new Uppy() }
}

Access instance via methods inside a custom Vue component

I'm trying to access the instance methods/data through a triggered method inside a custom registered Vue component.
Below a basic example:
Vue.component('example-component', {
template: `<div>
<h2>Count: {{count}}</h2>
<button class="btn btn-primary" v-on:click="increment()">Increment</button>
</div>`,
data: () => {
return {
count: 0
}
},
methods: {
increment: () => {
console.log("Click!");
console.log("Current count: ", this.count);
this.count++;
console.log("New count: ", this.count);
},
decrement: () => {
// other function
}
},
mounted: () => {
console.log("Example component mounted!");
}
});
Results:
Example component mounted!
Click!
Current count: undefined
New count: NaN
As you might notice the property 'count' has been loaded during the component mount and is available/rendered inside the HTML. The method 'increment()' has also been triggered. However, 'this.count' seems to be unreachable like possible other methods (e.g. 'this.decrement()') which will throw a TypeError this.decrement is not a function.
Any suggestions if this approach is even possible?
PS. I'm aware of the default approach via a .vue file registery like:
Vue.component('example-component', require('./components/ExampleComponent.vue').default);
Explanation from the official docs:
Vue automatically binds the this value for methods so that it always refers to the component instance. This ensures that a method retains the correct this value if it's used as an event listener or callback. You should avoid using arrow functions when defining methods, as that prevents Vue from binding the appropriate this value.
The answer above by Phoenix seems to be valid, and I can only add that you can write the functions in a short form too like:
increment() { ... },
decrement() { ... }
which looks nicer in my opinion, although there is a slight difference.
Arrow functions don't bind with this. Use normal functions instead for your methods.
increment: function() { ... },
decrement: function() { ... }

Make Vue template wait for global object returned by AJAX call

I'm trying to wait for certain strings in a sort of dictionary containing all the text for buttons, sections, labels etc.
I start out by sending a list of default strings to a controller that registers all the strings with my CMS in case those specific values do not already exist. After that I return a new object containing my "dictionaries", but with the correct values for the current language.
I run the call with an event listener that triggers a dispatch() on window.onload, and then add the data to a Vuex module state. I then add it to a computed prop.
computed: {
cartDictionary() {
return this.$store.state.dictionaries.myDictionaries['cart']
}
}
So now here's the problem: In my template i try to get the values from the cartDictionaryprop, which is an array.
<h2 class="checkout-section__header" v-html="cartDictionary['Cart.Heading']"></h2>
But when the component renders, the prop doesn't yet have a value since it's waiting for the AJAX call to finish. And so of course I get a cannot read property of undefined error.
Any ideas on how to work around this? I would like to have the dictionaries accessible through a global object instead of passing everything down through props since it's built using atomic design and it would be insanely tedious.
EDIT:
Adding more code for clarification.
My module:
const dictionaryModule = {
namespaced: true,
state: {
dictionaries: []
},
mutations: {
setDictionaries (state, payload) {
state.dictionaries = payload
}
},
actions: {
getDictionaries ({commit}) {
return new Promise((resolve, reject) => {
Dictionaries.init().then(response => {
commit('setDictionaries', response)
resolve(response)
})
})
}
}
}
My Store:
const store = new Vuex.Store({
modules: {
cart: cartModule,
search: searchModule,
checkout: checkoutModule,
filter: filterModule,
product: productModule,
dictionaries: dictionaryModule
}
})
window.addEventListener('load', function () {
store.dispatch('dictionaries/getDictionaries')
})
I think you can watch cartDictionary and set another data variable.
like this
<h2 class="checkout-section__header" v-html="cartHeading"></h2>
data () {
return {
cartHeading: ''
}
},
watch: {
'cartDictionary': function (after, before) {
if (after) {
this.cartHeading = after
}
}
}
Because this.$store.state.dictionaries.myDictionarie is undefined at the the begining, vuejs can't map myDictionarie['core']. That's why your code is not working.
You can do this also
state: {
dictionaries: {
myDictionaries: {}
}
}
and set the dictionaries key values during resolve.
I also would have liked to see some more of your code, but as i can't comment your questions (you need rep > 50), here it goes...
I have two general suggestions:
Did you setup your action correctly? Mutations are always synchronous while actions allow for asynchronous operations. So, if you http client returns a promise (axios does, for example), you should await the result in your action before calling the respective mutation. See this chapter in the official vuex-docs: https://vuex.vuejs.org/guide/actions.html
You shouldn't be using something like window.onload but use the hooks provided by Vue.js instead. Check this: https://v2.vuejs.org/v2/guide/instance.html#Lifecycle-Diagram
EDIT: As a third suggestion: Check, whether action and mutation are called properly. If they are handled in their own module, you have to register the module to the state.

Can I handle back button within methods in vuejs 2?

I need some help in vuejs 2. I want to detect back button pressed event. I did some research and found this,
document.addEventListener("backbutton", yourCallBackFunction, false");
I think it is global event. I need something local, within a method. where i can use some logic.
methods: {
backButtonPressed() {
}
}
Or can i bind the global one to local function? Can anyone help me with that? TIA
Add the event on your mounted method on your root Vue component (the one the Vue instance is tied to.
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
},
methods: {
yourCallBackFunction () {
// Your logic
}
}
mounted () {
document.addEventListener("backbutton", this.yourCallBackFunction, false);
},
beforeDestroy () {
document.removeEventListener("backbutton", this.yourCallBackFunction);
}
})
We also remove it on beforeDestroy to keep things tidy.
Note: I've not personally used the back button event so have added it to this example only because you say it's working but need a way to globally handle it. This code will do just that.
Note: As per #Luke's comment - we add the listener in the mounted event so it doesn't execute for in the SSR context. If SSR isn't a factor / consideration then we can use the created lifecycle hook.
If still someone come across this issue.
A solution for an event listener for browser-back is https://developer.mozilla.org/de/docs/Web/API/WindowEventHandlers/onpopstate
window.onpopstate = function() {
alert('browser-back');
};
Is easy, if you need to catch that behavior only your component, you can use beforeRouteLeave function in the root of your component.
Example:
beforeRouteLeave (to, from, next) {
const answer = window.confirm('Do you really want to leave?)
if (answer) {
next()
} else {
next(false)
}
}
But if you need to add this behavior globally, you need catch with beforeEnter in the routes.
If you are using vue-router(no idea if you don't, why...) a good solution is to use in your component:
beforeRouteLeave(to, from, next) {
if (from.name === 'nameOfFromRoute' && to.name === 'nameOfToRoute' ) {
next(false);
} else {
next();
}
console.log({ to, from });
},
This was one variation I found to work as well, a little less verbose and uses router.push in the beforeDestroy lifecycle method
Listen for popstate
Push the desired name/path to redirect
The code below would be a better understanding.
beforeDestroy() {
window.addEventListener("popstate", (event) => {
this.$router.push({ path: <your path> });
});
},
This implementation was on Nuxt 2.14.6 and works just as well with all versions of Vue.
I have a similar problem and solved using #click="backFunction"
and created the function on methods like this:
methods: {
backFunction(){
//yourlogic
},

VueJS programmatically trigger update on watched variable

Suppose I have a watcher that is watching an array mylist
watch {
mylist:function(){
//Code
}
}
How can I programatically trigger an update on the variable and run the code (if the variable is unchanged)? How can I trigger an update if the watched variable is a nested object/array or a simple string?
Since you didn't provide any description regarding the actual use-case, the best you can probably do is to extract a method that is called in call this method manually.
data: function() {
return { myList: [] }
},
watch: {
myList: this.handleListChange
},
methods: {
handleListChange: function(a, b) {
// .. do whatever you want to do in the watch method
}
}
To trigger the "watch" method you can now simply call your handleListChange method manually.
There are 2 questions:
How can I programatically trigger an update on the variable and run the code if the variable is unchanged?
You can hack it this way:
this._watchers.find(w => w.expression === "mylist").cb(this.mylist);
However, it's not part of the official Vue API. The _watchers list is rather an implementation detail. It may change in the future without any notice.
How can I trigger an update if the watched variable is a nested object/array?
Use a deep watcher:
watch: {
mylist: {
handler: function(val) {
// Code
},
deep: true
}
}
Working example: https://jsfiddle.net/LukaszWiktor/zjagahq2/
Here is how i did it
Frank was on to something, i just couldn't access the "this" context like suggested
so all had to do was pass all the args to the method.
watch: {
myList: (...args) { this.handleListChange(...args) },
}