vue computed cannot get the the attributes in the object - vue.js

Account to the defind of vuex
// inside mutations
mutations: {
updateMessage (state, message) {
state.obj.message = message
}
}
// html
<input v-model="message">
// ...
computed: {
message: {
get () {
return this.$store.state.obj.message
},
set (value) {
this.$store.commit('updateMessage', value)
}
}
}
and I code this
<input type="text" v-model="data.reference">
data () {
return {
data:{
...
reference: '',
}
}
},
computed: {
'data.reference':{
get () {
return this.$store.state.currentKbdata.reference
},
set (value) {
console.log(222)
this.$store.commit('updateReference', value)
}
}
}
And when i enter the input the 222 is not show up
.........................................................................

You cannot define computed getters with dot notation like you can watchers. Here's a fiddle of that not working, where you can see the error in the console reads:
Cannot read property 'reference' of undefined.
Also, it appears that you are attempting to define a computed property that already exists as a property defined in the data method. In this fiddle, you can see that that also won't work. The value defined in the data method takes precedence over the computed property definition.
Anyways, from your example, I don't see why you need to create a computed property on a nested object property at all.
Just use a normal definition for a computed property:
const store = new Vuex.Store({
state: {
reference: '',
},
mutations: {
updateReference(state, reference) {
state.reference = reference;
}
}
});
new Vue({
el: '#app',
store,
computed: {
reference: {
get() {
return this.$store.state.reference;
},
set(val) {
this.$store.commit('updateReference', val);
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/3.0.1/vuex.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.15/vue.min.js"></script>
<div id="app">
<input type="text" v-model="reference">
{{ $store.state.reference }}
</div>
If you really need to set the value of data.reference, then there are many ways you could make that happen. One would be to turn it into a computed property as well:
const store = new Vuex.Store({
state: {
reference: '',
},
mutations: {
updateReference(state, reference) {
state.reference = reference;
}
}
});
new Vue({
el: '#app',
store,
computed: {
reference: {
get() {
return this.$store.state.reference;
},
set(val) {
this.$store.commit('updateReference', val);
}
},
data() {
return { reference: this.reference };
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/3.0.1/vuex.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.15/vue.min.js"></script>
<div id="app">
<input type="text" v-model="reference">
<div>data.reference: {{ data.reference }}</div>
<div>$store.state.reference: {{ $store.state.reference }}</div>
</div>

Related

Vue computed methods can't change data using setter in template

I need change data using computed:
<template>
<div>{{ userDataTest }}</div>
</template>
props: {
exampleData: {
type: Object,
required: true,
},
},
computed: {
userDataTest: {
get: function() {
return this.exampleData;
},
set: function(newValue) {
console.log(newValue);
return newValue;
},
},
}
mounted () {
setTimeout(() => {
console.log('Change now to null!');
this.userDataTest = null;
}, 5000);
},
I get data using props, next I create computed methods with getter and setter. I added userDataTest in <template>. And the I change (using mounted) data in this.userDataTest to null using setter.
In console.log(newValue); in setter I see newValue is null, but in <template> nothing change still I have data from getter.
Why setter not change data in <template> to null ?
It seems you're trying to set the computed property's value by returning a new value, but Vue doesn't actually check the setter's return value. Perhaps you were trying to proxy a data variable through a computed property. If so, the setter should set that data variable in the setter body.
For instance, your component could declare a data variable, named userData, which always has the latest value of the exampleData prop through a watcher:
export default {
props: {
exampleData: Object
},
data() {
return {
userData: {}
}
},
watch: {
exampleData(exampleData) {
this.userData = exampleData
}
},
}
Then, your template and computed prop would use userData instead:
<template>
<div>{{ userData }}</div>
</template>
<script>
export default {
//...
computed: {
userDataTest: {
get() {
return this.userData
},
set(newValue) {
this.userData = newValue
}
}
}
}
</script>
Mutating a prop locally is considered an anti-pattern
However, you can use the .sync modifier as shown below, but you can't set the prop to null because you are specifying that it has to be an Object type.
Vue.component('my-component', {
template: `<div>{{ userDataTest }}</div>`,
props: {
exampleData: {
type: Object,
required: true
}
},
computed: {
userDataTest: {
get: function() {
return this.exampleData
},
set: function(newValue) {
this.$emit('update:exampleData', newValue)
}
}
},
mounted() {
setTimeout(() => {
console.log('Change now!')
this.userDataTest = {}
}, 2500)
}
})
new Vue({
el: '#app',
data() {
return {
exampleData: {
foo: 'bar'
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<div id="app">
<my-component :example-data.sync="exampleData"></my-component>
</div>

Vue v-model data is from ajax undefined value

I used the vue 2. I had a data from ajax, this is my code example:
<template>
<div>
<input type="input" class="form-control" v-model="siteInfo.siteId">
<input type="input" class="form-control" v-model="siteInfo.info.name">
<input type="input" class="form-control" v-model="siteInfo.accountData.name">
</div>
</template>
<script>
export default {
name: 'Site',
data() {
return {
siteInfo: {},
/* siteInfoName: '', */
}
},
/*computed: {
siteInfoName: function() {
return siteInfo.info.name || '';
},
...
},*/
methods: {
getData() {
// do ajax get data
this.$http.post('URL', {POSTDATA}).then(response => {
/*
response example
{ body:
data: {
sitdeId: 1,
info: { name: 'test'},
accountData: { name: 'accountTest'},
}
}
*/
this.siteInfo = response.body.data;
})
}
},
mounted() {
this.getData();
}
}
</script>
I got a warring message
[Vue warn]: Error in render: "TypeError: Cannot read property 'name'
of undefined"
I can use computed to fix it, but if I had a lot model, I should
write a lot computed.
I should create a lot data for those model?
I should not use an object to bind a lot model?
Does it have another solution for this situation? Thanks your help.
Before the data loads siteInfo.info will be undefined, so you can't access name in the v-model:
v-model="siteInfo.info.name"
Likewise for siteInfo.accountData.name.
My suggestion would be to set the initial value of siteInfo to null and then put a v-if="siteInfo" on the main div. Alternatively you could put a v-if on the individual input elements that checks for siteInfo.info and siteInfo.accountData.
You may also want to consider showing alternative content, such as a load mask, while the data is loading.
Don't be worried about too many v-models - you can do an iteration on the Object - like with Object.entries().
Vue.component('list-input-element', {
props: ['siteLabel', 'siteInfo'],
template: '<div><label>{{siteLabel}}<input type="input" class="form-control" v-model="siteInfo"></label></div>'
})
new Vue({
name: 'Site',
el: '#app',
data() {
return {
siteInfo: {},
}
},
methods: {
getData() {
// using mockup data for this example
fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(response => response.json())
.then(json => {
console.log(json)
this.siteInfo = json
})
// do ajax get data
/*this.$http.post('URL', {
POSTDATA
}).then(response => {
this.siteInfo = response.body.data;
})*/
}
},
mounted() {
this.getData();
}
})
div {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<list-input-element v-for="siteInfo in Object.entries(siteInfo)" :site-label="siteInfo[0]" :site-info="siteInfo[1]" />
</div>
Rounding up
So, when you do the single file template, use a computed value, and return an Object from that.
Base your v-for on that computed, and you'll have no problems.
Something like this:
<template>
<div>
<input type="input" class="form-control" v-for="infoEl in siteInfoComputed" v-model="infoEl">
</div>
</template>
<script>
export default {
name: 'Site',
data() {
return {
siteInfo: {},
}
},
computed: {
siteInfoComputed: function() {
// you could check for all the keys-values you want here, and handle
// 'undefined' problem here
// so, actually you "create" the Object here that you're going to use
let ret = {}
// checking if this.siteInfo exists
if (Object.keys(this.siteInfo).length) ret = this.siteInfo
return ret
},
},
methods: {
getData() {
// do ajax get data
this.$http.post('URL', {POSTDATA}).then(response => {
/*
response example
{ body:
data: {
sitdeId: 1,
info: { name: 'test'},
accountData: { name: 'accountTest'},
}
}
*/
this.siteInfo = response.body.data;
})
}
},
mounted() {
this.getData();
}
}
</script>

V-bind not updating class on store state change

I am pretty new to Vue and Nuxt. I am trying to get my head around $stores.
I created a state object and gave it a property which is a simple boolean. I'd like to add a class to an element depending on whether or not that property is true. Here's how I created the store:
const store = () => {
return new Vuex.Store({
state: {
foo: "You got the global state!",
userSidebarVisible: true
},
})
}
In my vue file I have the following:
<template>
<div>
<div>Hello!</div>
<button v-on:click="showSidebar">Click</button>
<div v-bind:class="{active: userSidebarVisible}">the sidebar</div>
<div>{{$store.state.userSidebarVisible}}</div>
</div>
</template>
<script>
export default {
data: function() {
return {
userSidebarVisible: this.$store.state.userSidebarVisible,
}
},
methods: {
showSidebar: function() {
if (this.$store.state.userSidebarVisible === true) {
this.$store.state.userSidebarVisible = false;
} else {
this.$store.state.userSidebarVisible = true;
}
}
}
}
</script>
When I click the button, the active class doesn't toggle, but the text within the last <div> does get updated. I am wondering what I am doing wrong here. Doing the same thing with local data property seems to work as intended.
First of all, you should not change the $store state outside of a mutation.
You need to add a mutation method to your store for updating userSidebarVisible:
state: {
userSidebarVisible: true
},
mutations: {
SET_USER_SIDEBAR_VISIBLE(state, value) {
state.userSidebarVisible = value;
}
}
Secondly, if you want your Vue instance's data to reflect the state data, you can make userSidebarVisible a computed property with getter and setter functions:
computed: {
userSidebarVisible: {
get() {
return this.$store.state.userSidebarVisible;
},
set(value) {
this.$store.commit('SET_USER_SIDEBAR_VISIBLE', value);
}
}
}
Here's an example:
const store = new Vuex.Store({
state: {
userSidebarVisible: true
},
mutations: {
SET_USER_SIDEBAR_VISIBLE(state, value) {
state.userSidebarVisible = value;
}
}
})
new Vue({
el: '#app',
store,
computed: {
userSidebarVisible: {
get() {
return this.$store.state.userSidebarVisible;
},
set(value) {
this.$store.commit('SET_USER_SIDEBAR_VISIBLE', value);
}
}
},
methods: {
toggleSidebar() {
this.userSidebarVisible = !this.userSidebarVisible;
}
}
})
.active {
color: green;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.4/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/2.4.0/vuex.min.js"></script>
<div id="app">
<button v-on:click="toggleSidebar">Click</button>
<div v-bind:class="{active: userSidebarVisible}">the sidebar</div>
<div>Global state: {{$store.state.userSidebarVisible}}</div>
<div>Vue instance state: {{userSidebarVisible}}</div>
</div>

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>

V-model with props & computed properties

I have a checkbox component that tracks whether or not an item has been saved by the user as a favorite. This information is passed in as a prop.
Because we can't/shouldn't mutate props passed in from a parent component, I am using v-model on a computed property.
<template>
<input class="favorite" type="checkbox" v-model="checked">
</template>
<script>
module.exports = {
props: ['favorite'],
computed: {
checked: {
get: function getChecked() {
return this.favorite;
},
set: function setChecked(newVal) {
this.$emit('update:favorite', newVal);
}
}
}
};
</script>
The parent component controls sending requests to the favorites api & updating the state of each entity if/when the request is successful.
<template>
<input-favorite
#update:favorite="toggleFavorite"
:favorite="entity.favorite"
></input-favorite>
</template>
<script>
module.exports = {
methods: {
toggleFavorite: function toggleFavorite(val) {
if (val) {
this.$store.dispatch('postFavorite', { id: this.entity.id, name: this.entity.name });
} else {
this.$store.dispatch('deleteFavorite', this.entity.id);
}
}
}
};
</script>
If the request fails, however, is it possible to prevent the checkbox from getting checked in the first place? Both this.favorite and this.checked stay in sync, but the state of the checkbox does not.
Because the data & props stay correct, I'm also having trouble figuring out how I could trigger a re-render of the checkbox to get it back to the correct state.
I suspect the problem is that favorite never changes, so Vue doesn't see a need to update. You should update it to true upon receiving the checked value (so state is consistent) and then update it again to false when the request fails.
Vue.component('inputFavorite', {
template: '#input-favorite',
props: ['favorite'],
computed: {
checked: {
get: function getChecked() {
return this.favorite;
},
set: function setChecked(newVal) {
this.$emit('update:favorite', newVal);
}
}
}
});
new Vue({
el: '#app',
data: {
entity: {
favorite: false
}
},
methods: {
toggleFavorite: function toggleFavorite(val) {
if (val) {
console.log("Post");
this.entity.favorite = true;
// Mock up a failure
setTimeout(() => {
console.log("Failed");
this.entity.favorite = false;
}, 250);
} else {
console.log("Delete");
}
}
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<template id="input-favorite">
<input class="favorite" type="checkbox" v-model="checked">
</template>
<div id="app">
<input-favorite #update:favorite="toggleFavorite" :favorite="entity.favorite"></input-favorite>
</div>
The way you have set this up lends itself to the recently-reintroduced .sync modifier, which would simplify your HTML a bit:
<input-favorite :favorite.sync="entity.favorite"></input-favorite>
Then you do away with toggleFavorite and instead add a watch:
watch: {
'entity.favorite': function (newValue) {
console.log("Updated", newValue);
if (newValue) {
setTimeout(() => {
console.log("Failed");
this.entity.favorite = false;
}, 250);
}
}
}