VueJS programmatically trigger update on watched variable - vue.js

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

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

Dynamic post entry

I'm trying to create pagination for my site on Vue.js.
I have two buttons:
<button #click.prevent="onPrevBtnClick">previous</button>
<button #click.prevent="onNextBtnClick">next</button>
Which pass to my main component:
methods: {
onPrevBtnClick () {
this.$emit('prev-button')
},
onNextBtnClick () {
this.$emit('next-button')
}
}
Basically, I want to make a query according to currentPage
async fetchMovies () {
try {
const fetchData = await axios.get(`${process.env.VUE_APP_API_URL}/discover/movie&api_key=${process.env.VUE_APP_API_KEY}&page=${this.currentPage}`)
this.movies = {...fetchData.data.results}
}
catch (e) {
console.log(e)
}
},
async onPrevBtnClick () {
this.currentPage--
},
async onNextBtnClick () {
this.currentPage++
}
I try to change them through a method, also tried through the computed but for some reason will not change dynamically.
I will be grateful for your help.
If you want this.movies to be updated when the user clicks "previous" or "next" you'll have to call the fetchMovies method in the onPrevBtnClick and onNextBtnClick methods. Currently onPrevBtnClick and onNextBtnClick only update this.currentPage, but doing so will not automatically cause fetchMovies to execute if fetchMovies is a method.
An alternative approach would be to make this.movies a computed property that is based on this.currentPage and get rid of fetchMovies, putting that logic in the computed property.
Check out the guide on Computed Properties and Watchers for more information. The key things to understand are:
"computed properties are cached based on their reactive dependencies. A computed property will only re-evaluate when some of its reactive dependencies have changed."
Methods never reevaluate on their own like computed properties. Methods need you to explicitly call them.

Vue: How to avoid using setTimeout in mounted() to wait for props to be available?

I find myself in a pattern of using setTimeout a lot to wait for the initial data to be loaded and available in my Vue components. Often, I'll be try to access an initial data and it won't be ready. Example:
data: {
return() {
name: null
}
},
props: {
otherName: {type: String}
},
mounted() {
if (this.otherName == "Bob" {
// do something
}
}
Would this pattern work? I was thinking about this but how often would this fire? Would I be repeatedly be firing this? At least with mounted, I know it happens ONCE and only once.
computed: {
getOtherName() {
return this.otherName;
}
},
watch: {
getOtherName(newValue, oldValue) {
if (oldValue != null && newValue == "Bob) {
//do something -- the prop will be null though so I use a setTimeout to wait for it to be available and not null
}
}
}
Watched properties will fire every time there is a detected change to the property, so that solution isn't quite ready. It's good that you're moving away from using timeouts, though, because those are just silly, especially since you're defeating the purpose of Vue being an MVVM framework.
If you want to have an initialization step, then you have two reasonable options:
You can mount the component ahead of time and then track an initialized flag to determine whether or not you've performed that initialization step.
You can defer mounting the component until your data is ready in the first place.
Option 1 will look something like this:
data() {
return {
initialized: false
};
},
watch: {
getOtherName(newValue, oldValue) {
if(this.initialized || oldValue !== null) {
return;
}
// Perform initialization logic.
this.initialized = true;
}
}
Option 2 will look something like this:
// HTML template
<my-component v-if="data_source !== null" :otherName="data_source"></my-component>
// JS
mounted: {
// Perform initialization logic.
}

How to access Vue Instance from within an argument function?

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

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.