Vuejs need to watch bool multiple time - javascript

I'm using VueJs and I need to watch a bool multiple time and when its true, do an action. I have found that the watch doesn't trigger when the current value is change for the same value (https://github.com/vuejs/vue/issues/1302).
For the moment, I just set the bool to false before every action where I can change the value and inside my watch I have a if where I check if the bool is true. Someone know a better way to do that?
watch: {
'$store.state.recommandationModule.recommandationSetCorrectly': function (val) {
if (val) {
// Do action
}
}
}

There is no event that happens when you assign a reactive item the same value it already had. You need to do something to catch those assignments.
You can use a settable computed for this. It will be called any time you try to assign a value to the variable. You will just need to ensure that you set your computed, and not the store variable that it is based on.
new Vue({
el: '#app',
data: {
storeStateMock: true,
trueCount: 0,
changeCount: 0
},
computed: {
recommendationSetCorrectly: {
get() { return this.storeStateMock; },
set(v) {
if (v) { ++this.trueCount; }
this.storeStateMock = v;
}
}
},
watch: {
recommendationSetCorrectly() {
++this.changeCount;
}
},
created() {
setInterval(() => {
this.recommendationSetCorrectly = Math.random() > 0.5;
}, 1200);
}
});
<script src="//unpkg.com/vue#latest/dist/vue.js"></script>
<div id="app">
{{ recommendationSetCorrectly }} {{ trueCount }} {{ changeCount }}
</div>
You could also add a trueCount item to your $store, and increment it in the mutation that sets your boolean. Then you could watch trueCount.

Related

Vue custom directive to simulate getter/setter of computed property

There is an input tag when user type something, I want to add some symbols to the input value. Well, when I need the real value I should remove those symbols from the value. I know I can achieve this using computed property like following:
<template>
<input type="text" v-model="computedTest" /><!-- shows 20$ -->
</template>
<script>
export default {
data() {
return {
test: 20,
}
},
computed: {
computedTest: {
get() {
return this.test + "$"
},
set(val) {
this.test = this.val.replace(/$/g, "")
},
},
methods: {
doSomething() {
console.log(this.test) // 20
},
},
},
}
</script>
Since Vuejs can do this using computed property feature, I believe I can achieve this feature using custom directive. But, I have no idea of the way to do. This is the first try what I did:
<template>
<input type="text" v-model="test" v-custom />
</template>
<script>
export default {
data() {
return {
test: 0
}
},
}
</script>
And the code for v-custom directive:
export default {
bind: function(el, binding, vnode) {
Vue.set(el, "value", toLanguage(el.value, "en")) // toLanguage is a custom function
},
componentUpdated: function(el, binding, vnode) {
// getContext is a custom function and the `contextObj` variable will be equal to
// the whole data property of the component context and the `model` will be equal to
// the v-model expression (in this example, `test`).
let { contextObj, model } = getContext(vnode)
const vnodeValue = get(contextObj, model) // lodash `get` function
// parsing v-model value to remove symbols
const parsedValue = parseInputValue(el, vnodeValue) ?? vnodeValue
if (contextObj) {
Vue.set(contextObj, model, parsedValue)
}
el.value = toLanguage(el.value, "en")
el.dispatchEvent(new Event("input", { bubbles: true }))
},
}
But this directive snippet creates an infinite loop. I'm using Vue2.x. I'll appreciate anybody can help.

How to pass object handler into vue component

I want to cache state inside a handler passed by props.
Problem is, the template renders and text content changes well, but the console always throw error:
[Vue warn]: Error in v-on handler: "TypeError: Cannot read property 'apply' of undefined"
Codes below:
<template>
<picker-box :options="item.options" #change="handlePickerChange($event, item)">
<text>{{ item.options[getPickerValue(item)].text }}</text>
</picker-box>
</template>
<script>
export default {
props: {
item: {
type: Object,
default() {
return {
options: [{ text: 'op1' }, { text: 'op2' }],
handler: {
current: 0,
get() {
return this.current
},
set(value) {
this.current = value
},
},
/* I also tried this: */
// new (function () {
// this.current = 0
// this.get = () => {
// return this.current
// }
// this.set = (value) => {
// this.current = value
// }
// })(),
}
},
},
},
methods: {
handlePickerChange(value, item) {
item.handler.set(value)
},
getPickerValue(item) {
return item.handler.get()
},
},
}
</script>
I know it's easy using data() or model prop, but I hope to cahce as this handler.current, in other words, I just want to know why this handler object isn't correct (syntax layer), how can I fix it.
What exactly state are you trying pass?
If you need to keep track of a static property, you could use client's localstorage, or a watcher for this.
If you need to keep of more dynamic data, you could pass it to a computed property that keeps track and sets them everytime they change.

Static vue filtered Array is empty when I need it

I have a template that needs some non-reactive data in it from my Vuex store. However at the moment, I have to manually switch views to get the data to load. I am assuming I should not use mounted or created. If I use watch, then it basically becomes reactive again, and I only want to get this once.
data: function () {
return {
localreadmode: false,
myArray: null,
}
},
computed: {
...mapState({
myNodes: (state) => state.myNodes,
configPositions: (state) => state.configPositions,
configEmoji: (state) => state.configEmoji,
}),
nodes_filtered: function () {
return this.myNodes.filter((nodes) => {
return nodes.deleted == false
})
},
},
// this is to stop sync chasing bug
myArray: null,
created() {
this.$options.myArray = this.nodes_filtered
console.log(this.nodes_filtered)
// is empty unless I switch views
},
You could still use a watcher that runs only once via the vm.$watch API. It returns a method that can be called to stop watching the value, so your handler could invoke it when nodes_filtered[] is not empty.
export default {
mounted() {
const unwatch = this.$watch(this.nodes_filtered, value => {
// ignore falsy values
if (!value) return
// stop watching when nodes_filtered[] is not empty
if (value.length) unwatch()
// process value here
})
}
}
demo

Vue: Accessing data from an unmounted component

I have an issue where I want to retreive data from a child component, but the parent needs to use that data, before the child is mounted.
My parent looks like this
<template>
<component :is="childComp" #mounted="setData"/>
</template>
<script>
data : {
childComp : null,
importantData : null
},
methods : {
addComponent : function() {
this.prepareToAdd(this.importantData);
this.childComp = "componentA"; //sometimes will be other component
},
setData : function(value) {
this.importantData = value;
},
prepareToAdd : function(importantData){
//something that has to be run before childComp can be mounted.
}
}
</script>
My child (or rather, all the potential children) would contain something like this:
<script>
data : {
importantData : 'ABC',
},
created: function() {
this.$emit('mounted', this.importantData);
},
</script>
This clearly doesn't work - importantData is set when the childComponent is mounted, but prepareToAdd needs that data first.
Is there another way of reaching in to the child component and accessing its data, before it is mounted?
You can use $options to store your important data and have it available in beforeCreate. You can also use it to initialize a data item, and you can emit data items in created (you don't have to initialize from $options to emit in created, I'm just pointing out two things that can be done). The $options value is, itself, reactive (to my surprise) and can be used like any data item, with the added benefit that it is available before other data items.
new Vue({
el: '#app',
methods: {
doStuff(val) {
console.log("Got", val);
}
},
components: {
theChild: {
template: '<div>Values are {{$options.importantData}} and {{otherData}}</div>',
importantData: 'abc',
data() {
return {
otherData: this.$options.importantData
};
},
beforeCreate() {
this.$emit('before-create', this.$options.importantData);
},
created() {
this.$emit('on-created', this.otherData + ' again');
// It's reactive?!?!?
this.$options.importantData = 'changed';
}
}
}
});
<script src="//unpkg.com/vue#latest/dist/vue.js"></script>
<div id="app">
<the-child #before-create="doStuff" #on-created="doStuff"></the-child>
</div>
My bad :(
We cannot get the data inside beforeCreated() hook.
Use the beforeCreate() hook instead of created() hook:
beforeCreate: function() {
this.$emit('mounted', this.importantData);
},
We can use a watcher or computed option, so now your parent component would look:
data: {
childComp: null,
importantData: null,
isDataSet: false
},
methods: {
addComponent: function() {
this.prepareToAdd(this.importantData);
this.childComp = "componentA"; //sometimes will be other component
},
setData: function(value) {
this.importantData = value;
this.isDataSet = true;
},
prepareToAdd: function(importantData) {
//something that has to be run before childComp can be mounted.
}
},
watch: {
isDataSet: function(newValue, oldValue) {
if (newValue) {
this.addComponent();
}
}
}
I would suggest to use computed method as it caches the results. Use watcher when you have to perform asynchronous code.

vuejs component array push watcher

confusing title, I wasn't sure how else to write it.
So i have a component and it accepts a prop which is an array.
What i am looking to do, is on array.push() i would like a function to trigger for that array element.
so it is an alert toast message, and i want it to stay up for 3 seconds. so i was thinking about something like
watch: {
arrayObj: function () {
let self = this
setTimeout(function() {
self.dismiss(this.id)
}, 3000)
}
}
but i think i may be missing something here. How do i get the latest pushed object's reference so i make sure the dismiss method calls the correct alert? Is watch even the correct way to go about this?
I'm not entirely clear on what behavior you're trying to get, but it seems to me that you need to keep track of the latest pushed item and pass that to your component. The component would watch latestPushed and act accordingly.
If the latest pushed item could be repeated, you would not see a change when a duplicate value is pushed. In that case, it makes sense to pass an event bus prop to the component and send and event when the parent pushes.
new Vue({
el: '#app',
data: {
bus: new Vue(),
theArray: [1]
},
methods: {
push() {
const value = Math.floor(Math.random() * 10);
this.theArray.push(value);
this.bus.$emit('push', value);
}
},
components: {
toasterNote: {
props: ['bus'],
data() {
return {
active: false,
latestPush: null,
timer: null
};
},
created() {
this.bus.$on('push', (newValue) => {
this.latestPush = newValue;
this.active = true;
clearTimeout(this.timer);
this.timer = setTimeout(() => this.active = false, 3000);
});
}
}
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<div id="app">
<div>{{theArray}}</div>
<button #click="push">Push</button>
<toaster-note inline-template :bus="bus">
<div v-if="active">
You pushed {{latestPush}}
</div>
</toaster-note>
</div>

Categories

Resources