Computed property doesn't update upon changing data variable - vue.js

I have a data variable named monRaw that is a Moment object representing the first day of current week:
...
data() {
return {
monRaw: moment().startOf('isoWeek')
}
},
...
Then I have a computed property named weekTitle that displays it along with some text (it does more than that in my project, but I simplified it here):
...
computed: {
weekTitle: function() {
alert('updating computed property...');
return `Current week starting on ${this.monRaw.format('DD')}`;
}
},
...
Finally, a method named viewNextWeek() changes my data variable to the following week's first day:
...
methods: {
viewNextWeek: function() {
this.monRaw = this.monRaw.add(1, 'weeks');
},
...
In my template, I display my computed property along with a button triggering my method:
{{ weekTitle }}
<button #click="viewNextWeek">Next Week</button>
For some reason though, my computed property does NOT get updated when I change the data variable it's based on.
It doesn't even try to: my alert() gets triggered on page load only, not any more after that when clicking the button.
What's wrong here?

Moment.add mutates the moment variable, so you are just assigning the same object back to itself, which results in no change.
Per the docs:
If you want to create a copy and manipulate it, you should use
moment#clone before manipulating the moment.
new Vue({
el: '#app',
data: {
monRaw: moment().startOf('isoWeek')
},
computed: {
weekTitle: function() {
alert('updating computed property...');
return `Current week starting on ${this.monRaw.format('DD')}`;
}
},
methods: {
viewNextWeek: function() {
const newValue = this.monRaw.clone().add(1, 'weeks');
this.monRaw = newValue;
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/moment.min.js"></script>
<script src="https://unpkg.com/vue#latest/dist/vue.js"></script>
<div id="app">
{{ weekTitle }}
<button #click="viewNextWeek">Next Week</button>
</div>

Related

Vue Dynamic Form Values AND Keys, with pre existing value for render, why not rendering?

I am trying to create a dynamic 'quick input' form with Vue.
A simple text input that has a dynamic data key so that I can change what I'm submitting to axios. I couldn't figure out how to get a dynamic key name coming from a prop eg
data() {
return {
DYNAMIC-NAME-FROM-PROP: value
}
}
So I've got a values: {} bit of data that gets filled by the props instead.
The code below achieves everything EXCEPT pre-rendering the existing value.
See the comments next to v-model in the tempate:
<template>
<div class="relative">
<input
type="text"
v-model="values[fieldName]" // This is not rendering on load
// v-model="values[this.$props.field]" -> 'this' is null error #input
#keydown.enter.prevent="submit"
/>
</div>
</template>
<script>
export default {
props: ["userId", "field", "preFill"],
data() {
return {
values: {},
fieldName: this.$props.field,
};
},
mounted() {
this.values[this.$props.field] = this.$props.preFill;
},
methods: {
submit() {
axios.post(`/admin/${this.userId}/update`, this.values).then(response => {
// success
});
}
}
};
</script>
Am I going about this completely wrong?
Or am I 'nearly there' and just need to fix the v-model render issue?
To set a dynamic key name on an object in javascript, it turns out you can use square brackets, as in:
{
[keyName] : value
}
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#Computed_property_names
So my code is fixed by simply passing the prop as the key in the axios call:
submit() {
axios
.post(`/admin/${this.userId}/update`, {
[this.$props.field]: this.value
})
.then(response => {
// we out here
});
}

watch for the whole array as well as one of the properties of it

Let's say I have a prop such as
[{id:1, name:"first"}, {id:2, name:"second"}]
I have a watcher for this called Points. as soon as parent component changes this array, watcher function gets called in child.
Now, I also want to watch if name field in any of this array's object got changed. Workaround I know is to set deep as true in watcher, but this means this is gonna make the watcher for each property of the object. As I have a very huge array of huge objects, I don't want to make this many watcher.
So is there a way to make watcher for the whole array and also one of the properties of the array's object?
You can mark the non-reactive properties as non-configurable, and Vue will not detect changes in their values.
let arr = [{id:1, name:"first"}, {id:2, name:"second"}];
arr.forEach(item => Object.defineProperty(item, "id", {configurable: false}));
Alternatively, you could use a shallow watcher and require code that modifies the reactive properties to use Vue.set() instead of simple assignment.
Vue.set(arr[0], "name", "new name"); // don't use arr[0].name = "new name";
You can create a child component, where you bind objects to the array and place the watcher inside the child component as below:
Parent.vue
<template>
<child-component v-for="point in points" :point="point" ></child-component>
</template>
data: {
return {
points: [{id:1, name:"first"}, {id:2, name:"second"}]
}
}
Child.vue:
props: ['point']
...
watch: {
name: function(newVal){
// watch name field
}
}
One way would be to create a computed property that just touches the bits you care about and then watch that instead.
new Vue({
el: '#app',
data () {
return {
points: [{id: 1, name: 'first'}, {id: 2, name: 'second'}]
}
},
computed: {
pointStuffICareAbout () {
return this.points.map(point => point.name)
}
},
methods: {
updateId () {
this.points[0].id = Math.round(Math.random() * 1000)
},
updateName () {
this.points[0].name = Math.random().toString(36).slice(2, 7)
},
addItem () {
this.points.push({id: 3, name: 'third'})
}
},
watch: {
pointStuffICareAbout () {
console.log('watcher triggered')
}
}
})
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<div id="app">
<button #click="updateId">Update id</button>
<button #click="updateName">Update name</button>
<button #click="addItem">Add item</button>
<p>
{{ points }}
</p>
</div>

How to display the value which is inside computed in VueJS2?

How to display the value which is inside computed in VueJS2 ? Actually i'm trying to return the value click*2 using computed property. So i'm displaying the output using the expression {{counter}}. But i'm not getting any output, at first it was set to counter to 0 (stored in the data). Even i didn't use v-once. So why my counter value is not updating through computed property?
<div id="app4">
<p>counter : {{ counter }}</p>
<p>clicks : {{ click }}</p>
<button v-on:click="increment">increment</button>
</div>
<script>
var vm = new Vue({
el: '#app4',
data: {
counter:0,
click: 0,
},
methods: {
increment() {
this.click++;
}
},
computed: {
counter() {
return this.click * 2;
}
}
});
</script>
Remove counter from data and it will work. You have two properties that shadow one another - one in data and the other in computed.

Computed object not updated using sync modifier

I have a computed property that simply formats the date:
computed: {
items() {
return this.licenseItems.map(function(license) {
license.expires_at = moment(license.expires_at).format('MM/DD/YYYY');
return license;
});
}
}
I pass licenseItems to a form component with the .sync modifier and emit update:field event from the form. In vue dev tools, I can see that licenseItems (data) is properly updated, but items (computed) is still showing the old data so no re-computation was performed.
I have noticed that if I remove the map and just return the licenseItems object from the computed property, it is updated. Is there some issue with Vue's computed property on mapped objects when using the sync modifier?
You should be aware that you're modifying underlying objects in your computed. From your fiddle:
computed: {
mapped: function() {
return this.items.map(function(item) {
let original = item.date;
item.date = moment(item.date).format('MM/DD/YYYY');
console.log(original + ' => ' + item.date);
return item;
});
}
}
Your incrementDates function also modifies the underlying objects directly.
Because each element of items is an object, item is a reference to that same object, so your routine updates the members of items itself. If you intend to modify the object values, you should use a watch instead of a computed. If you want to have a proper computed that does not modify data items, you need to deep copy the objects.
In my example below, you can see that the data value and the computed value are distinct, but the computed is based on the data. Also, the update event works with sync to update the value in the parent, rather than the value being updated directly in the component. You can enter any format of date that Date understands to set the value.
new Vue({
el: '#app',
data: {
licenseItems: [{
expires_at: Date.now()
}]
},
computed: {
items() {
return this.licenseItems.map(function(license) {
const newItem = Vue.util.extend({}, license);
newItem.expires_at = moment(license.expires_at).format('MM/DD/YYYY');
return newItem;
});
}
},
components: {
myUpdater: {
props: ['items'],
methods: {
doUpdate(event, index) {
const newObj = this.items.map(item => Vue.util.extend({}, item));
newObj[index].expires_at = new Date(event.target.value);
console.log("new object", newObj);
this.$emit('update:items', newObj);
}
}
}
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.19.1/moment.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="app">
<my-updater :items.sync="licenseItems" inline-template>
<div>
<input v-for="item, index in items" :value="item.expires_at" #change="doUpdate($event, index)">
</div>
</my-updater>
<div v-for="item in items">
{{item.expires_at}}
</div>
</div>

Use computed property in data in Vuejs

How can I use a computed property in the data or emit it via bus?
I have the following vue instance, but myComputed is always undefined but computedData is working correctly.
var vm = new Vue({
data(){
return{
myComputed: this.computedData
}
},
computed: {
computedData(){
return 'Hello World'
}
}
})
Unfortunately, it is impossible to use computed property in data because of component creation timing: data evaluates Before computed properties.
To make things as simple as possible, just do the work in watcher, unless you want to emit the changes to different components or there're a lot of variables you want to notify, then you may have to use Vuex or the event bus:
var vm = new Vue({
data(){
return{
myComputed: '',
computedData: 'Hello World'
}
},
created() {
this.myComputed = this.computedData;
},
watch: {
computedData() {
this.myComputed = this.computedData;
}
}
});
Computed is already accessible in the template using {{ }}.
But you can use the
watch:{
//your function here
}
instead of computed
If you are using computed/reactive objects then it should be inside the computed and not inside the data.
Simply change your code to use computed instead of data
var vm = new Vue({
data(){
return{}
},
computed: {
computedData(){
return 'Hello World'
},
myComputed(){
return this.computedData
}
}
})
you are trying to use data as computed and this shall not be.
data doesn't act like computed object.
and it's not because of component creation timing. What if we changed the component creation timing ? this will not solve anything as data will take only the first computed value(only one) and will not update after.
you can work just with the computed function
var vm = new Vue({
data(){
return{
//is not necessary
}
},
computed: {
computedData(){
return 'Hello World'
}
}
})
and in your template
<template>
<div>{{ computedData }}</div>
</template>
You are over-coding it. Computed props are accessible in the same manner as data props in your template.
var vm = new Vue({
computed: {
myComputed(){
return 'Hello World'
}
}
})
In the template you have access to this just like you do to data:
<template>
<div>{{ myComputed }}</div>
</template>
https://v2.vuejs.org/v2/guide/computed.html
Try to convert the computed in a method
var vm = new Vue({
data(){
return{
myComputed: this.computedData
}
},
methods: {
computedData(){
return 'Hello World'
}
}
})
This is simple and it works (NOT reactive), but has a cost:
https://medium.com/notonlycss/the-difference-between-computed-and-methods-in-vue-js-9cb05c59ed98
computed is not available at the time data gets initialized.
If it should be a one-time thing (and NOT reactive), you could achieve this by setting the data at the moment where the computed property is available by using the created() hook:
export default {
data: () => ({
myDataBackend: '',
}),
computed: {
computedData () {
return 'Hello World'
}
},
created() {
this.$set(this, 'myDataBackend', this.computedData)
}
}
Futher reading: Vue Documentation on Lifecycle Hooks
In case you are trying to work with v-model:
You could also use :value and some event like #change or #keyup in the element instead.
:value is the value which the input-element initially works with
After writing some letter in the input field, the #keyup event changes the data.
Typically, events carry the updated form value in target.value
The changeMyData method sets the value
the computed property listens to the data change and the :value of the input field gets updated.
Note: I used data as a data store. But you could also use for example vuex instead.
<template>
<div>
<input
type="text"
:value="computedData"
#keyup="changeMyData"
/>
<p>{{myDataBackend}}</p>
</div>
</template>
<script>
export default {
data: () => ({
myDataBackend: 'Hello World'
}),
methods: {
changeMyData(evt) {
this.$set(this, 'myDataBackend', evt.target.value)
console.log('Changed the value to: ' + evt.target.value)
}
},
computed: {
computedData () {
return this.myDataBackend
}
}
}
</script>