I have these computed properties in a component:
computed: {
messageText: {
get() {
return this.getMessageProp('messageText') // Maps to a vuex getter
},
set(value) {
this.setMessageProp(['messageText', value]) // Maps to a vuex mutation
}
},
location: {
get() {
return this.getMessageProp('location')
},
set(value) {
this.setMessageProp(['location', value])
}
}
}
This works on browser refresh. As you can see it's a bit repetitive (There are a few more in other components).
I tried to create them dynamically like this:
data() {
return {
stepProps: {
messageText: {},
location: {},
}
}
},
created() {
Object.keys(this.stepProps).forEach((key) => {
if (!this.$options.computed[key]) {
this.$options.computed[key] = {
get() {
return this.getMessageProp(key)
},
set(value) {
this.setMessageProp([key, value])
}
}
}
})
}
But it does not work on browser refresh. I get the error:
Property or method "messageText" is not defined on the instance but referenced during render....
It works when switching components (I am using Vue router)
How can I get this, or something similar, to work? Ultimately I wanted to put it into a mixin to reuse in other components.
Related
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.
I'm quite new with Vue and Vuex so please bear with me.
I want to make the computed function versions() get called when I change state.template, but I'm failing to do so. More specifically, when state.template.versions changes.
This is part of the component that I want to re-render when state.template.versions changes. You can also see the computed property versions() which I want to be called:
<el-dropdown-menu class="el-dropdown-menu--wide"
slot="dropdown">
<div v-for="version in versions"
:key="version.id">
...
</div>
</el-dropdown-menu>
...
computed: {
...mapState('documents', ['template', 'activeVersion']),
...mapGetters('documents', ['documentVersions', 'documentVersionById', 'documentFirstVersion']),
versions () {
return this.documentVersions.map(function (version) {
const v = {
id: version.id,
name: 'Draft Version',
effectiveDate: '',
status: 'Draft version',
}
return v
})
},
This is the getter:
documentVersions (state) {
return state.template ? state.template.versions : []
},
This is the action:
createProductionVersion (context, data) {
return new Promise((resolve, reject) => {
documentsService.createProductionVersion(data).then(result => {
context.state.template.versions.push(data) // <-- Here I'm changing state.template. I would expect versions() to be called
context.commit('template', context.state.template)
resolve(result)
})
This is the mutation:
template (state, template) {
state.template = template
},
I've read that there are some cases in which Vue doesn't detect chanegs made to an array, but .push() seems to be detected. Source: https://v2.vuejs.org/v2/guide/list.html#Caveats
Any idea on why the computed property is not being called when I update context.state.template.versions?
The issue may come from state.template = template. You guessed correctly that it was a reactivity issue, but not from the Array reactivity, but the template object.
Vue cannot detect property addition or deletion. This includes affecting a complex object to a property. For that, you need to use Vue.set.
So your mutation should be :
template (state, template) {
Vue.set(state, "template", template)
},
https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats
Your function won't get called because this is wrong:
context.state.template.versions.push(data)
context.commit('template', context.state.template)
the context.state object just points to your current state nothing more.
My suggested solution will be:
First You need to declare your store state correctly
state: {
template: {
versions: []
}
}
You need to update your getter to look like this with no unnecessary
conditioning:
documentVersions: state => return state.template.versions,
add a new mutation
ADD_VERSION: (state, version) => {
state.template = {
...state.template,
versions: [...state.template.versions, version]
};
}
your action should like this now:
createProductionVersion({commit}, data) {
return new Promise((resolve, reject) => {
documentsService.createProductionVersion(data).then(result => {
commit('ADD_VERSION', data);
resolve(result);
});
});
}
In your component I suggest to change your computed property from a function
to an object that contains a get and set methods (set is optional)
versions: {
get() {
return this.documentVersions.map(function (version) {
const v = {
id: version.id,
name: 'Draft Version',
effectiveDate: '',
status: 'Draft version',
}
return v
})
}
},
I think this error occurred because you did not declare your store state correctly. Make sure you have the versions property in your template object.
state: {
template: {
versions: []
}
}
This way, any changes in the versions property will be detected by vue.
I'm trying to fetching some data for my search tree and i'm not able to get the data directly from axios or to call a function because it can't find this.
export default {
name: 'SideNavMenu',
data () {
return {
searchValue: '',
treeData: this.getData(),
treeOptions: {
fetchData(node) {
this.onNodeSelected(node)
}
},
}
},
In the data() I have treeOptions where I want to call a function called onNodeSelected. The error message is:
"TypeError: this.onNodeSelected is not a function"
can anybody help?
When using this, you try to call on a member for the current object.
In JavaScript, using the {} is actually creating a new object of its own and therefore, either the object needs to implement onNodeSelected or you need to call a different function that will allow you to call it on an object that implements the function.
export default {
name: 'SideNavMenu',
data () {
return {
searchValue: '',
treeData: this.getData(), // <--- This
treeOptions: {
fetchData(node) {
this.onNodeSelected(node) // <--- and this
}
},
}
},
//are calling functions in this object :
{
searchValue: '',
treeData: this.getData(),
treeOptions: {
fetchData(node) {
this.onNodeSelected(node)
}
},
//instead of the object you probably are thinking
I would avoid creating object blocks within object blocks like those as the code quickly becomes unreadable and rather create functions within a single object when needed.
I am guessing you would have the same error message if you tried to get a value from treeData as well
You are not calling the function, or returning anything from it. Perhaps you're trying to do this?
export default {
name: 'SideNavMenu',
data () {
return {
searchValue: '',
treeData: this.getData(),
treeOptions: fetchData(node) {
return this.onNodeSelected(node)
},
}
},
Regardless, it is not considered good practice to put functions inside data properties.
Try declaring your variables with empty values first, then setting them when you get the data inside beforeCreate, created, or mounted hooks, like so:
export default {
name: 'SideNavMenu',
data () {
return {
searchValue: '',
treeData: [],
treeOptions: {},
}
},
methods: {
getData(){
// get data here
},
fetchData(node){
this.onNodeSelected(node).then(options => this.treeOptions = options)
}
},
mounted(){
this.getData().then(data => this.treeData = data)
}
},
Or if you're using async await:
export default {
name: 'SideNavMenu',
data () {
return {
searchValue: '',
treeData: [],
treeOptions: {},
}
},
methods: {
getData(){
// get data here
},
async fetchData(node){
this.treeOptions = await this.onNodeSelected(node)
}
},
async mounted(){
this.treeData = await this.getData()
}
},
I'm pretty new to Vue, what I'm doing now is the following.
I receive an Item prop in my component, I spread this Item prop out over a Form data object that's defined in my component (as to have reactivity)
data() {
return {
form: {}
}
mounted () {
this.form = {
...this.item,
translations: { ...this.item.translations }
}
},
Now my local form data holds the information, including reactive translations, right?
Next thing I try to do is filter this data, but then it's failing me.
If I console.log(this.form). It is an Observable (see screenshot)
Is there a way to filter, reduce, map on this 'Observable'?
Am I doing 'reactivity' the right way?
Try clone/deepClone, before assigning the item to this.form.
You can access props from data() directly.
data() {
return {
form: {
...this.item,
translations: { ...this.item.translations }
}
}
},
computed: {
getForm() {
// use filter/map method here, e.g.
// return this.form.filter((item) => { ... })
}
}
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.