Why html shows the object returned by the computed property is ‘undefined’? - vue.js

I have a error at my vue project.I use computed to return a object.
computed: {
getOpLog() {
if (this.product_menu) {
this.product_menu.forEach(opLogItem => {
if(opLogItem.id === 'menu_item_oplog') {
return opLogItem;
}
});
}
}
},
and my debugger shows that I have the right return object.
But when i run it in brower, it just not work.
[Vue warn]: Error in render: "TypeError: Cannot read property 'hidden' of undefined"
Here is my html.
<el-menu-item v-if="getOpLog.hidden" :id="getOpLog.id">
...
</el-menu-item>
But when I use this
getOpLog() {
if (this.product_menu) {
return this.product_menu[8]
}
}
It work.I want to know how can i fix this.Thx

When your if condition inside the getter is false i.e. if (this.product_menu), then getter will return undefined object. And thus, Vue.js complains.
As a simple remedy, add an extra check in v-if like:
<el-menu-item v-if="getOpLog && getOpLog.hidden" :id="getOpLog.id">
...
</el-menu-item>
Further, using return inside the forEach function of an array doesn't really cause a return from actual getter function. It is just returning from the inner arrow function. You will need to modify your code using Array.prototype.find method:
computed: {
getOpLog() {
if (this.product_menu) {
const item = this.product_menu.find(opLogItem => {
return opLogItem.id === 'menu_item_oplog';
});
return item;
}
}
};
But, you still should have v-if check for getOpLog in case find method returns undefined value.

Related

Why Am I getting error return in computed property?

I'm using Vuex, inside Getter Foo function I'm returning two values inside array:
return ["Try Again"] or return ["Data result", data], in computed, I'm checking the array length and returning depending on result
computed:{
Foo: function(){
const getFoo = this.$store.getters.Foo;
if(getFoo.length === 1) {
this.existFoo = false
return getFoo[0]
}
this.existFoo = true
return getFoo
}
}
but I'm getting this error, even reading others posts I cannot solve it
34:9 error Unexpected side effect in "Foo" computed property
vue/no-side-effects-in-computed-properties
37:7 error Unexpected
side effect in "Foo" computed property
vue/no-side-effects-in-computed-properties
You are not permitted to change the state in computeds.
Try using another computed instead of existFoo
computed:{
Foo(){
if(this.$store.getters.Foo.length === 1) {
return this.$store.getters.Foo[0]
}
return this.$store.getters.Foo
},
existFoo(){
return this.$store.getters.Foo.length > 1
}
}
Now you should remove existFoo from state
You could use a watcher to watch the store value and set your local variables.
computed: {
getFooFromStore() {
return this.$store.getters.Foo
}
}
watch: {
getFooFromStore: function() {
this.existFoo = this.getFooFromStore[0] ? false : true;
}
}

How to passing computed with param to props of child component?

I'm a newbie in Vuejs. I'm trying passing computed with param to props of child component but it's error.
This is my code:
<FloatingInput
:type="'text'"
:name="'username'"
:error="fieldError('username')"
>
<FloatingInput
:type="'password'"
:name="'password'"
:error="fieldError('password')"
>
And in the script:
computed: {
fieldError: {
get: function () {
return this.error[field]; // will return false or message error
},
set: function (field) {
this.error[field];
}
},
}
Computed properties are meant to be pure. This means that computed properties rely solely on the state of the component, and should not have side-effects. This allows Vue to only recalculate computed properties whenever the data they rely on changes. For this reason you can not call a computed property. You must use a method.
Luckily for you, what you want to do is just a key lookup in an object, so you can just use a data attribute and lookup the field.
<FloatingInput
type="text"
name="username"
:error="fieldErrors['username']"
>
data () {
fieldErrors: {}
},
methods: {
setFieldError(field, error) {
this.$set(this.fieldErrors, field, error);
}
}

Vue warning when accessing nested object

I am not sure why I get a Vue warning when accessing nested object.
{{ user.area.name }}
[Vue warn]: Error in render: "TypeError: Cannot read property 'name' of undefined"
TypeError: Cannot read property 'name' of undefined
Just accessing the object has no warning.
{{ user.name }}
Any advice?
Totally guessing here but lets see if I'm right...
Say you've got something like this in your component / Vue instance data initialiser...
data () {
return {
user: {}
}
}
and you then populate that object asynchronously, eg
mounted () {
setTimeout(() => { // setTimeout is just an example
this.user = {
...this.user,
area: {
name: 'foo'
}
}
}, 1000)
}
If your template has
{{ user.area.name }}
when it initially renders before the asynchronous task has completed, you will be attempting to access the name property of area which is undefined.
Example ~ http://jsfiddle.net/tL1xbmoj/
Your options are...
Initialise your data with a structure that won't cause errors
data () {
return {
user: {
area: {
name: null
}
}
}
}
Example ~ http://jsfiddle.net/tL1xbmoj/1/
Use conditional rendering to prevent the error
<span v-if="user.area">{{ user.area.name }}</span>
Example ~ http://jsfiddle.net/tL1xbmoj/2/
Your user doesn't contain an area, so when you try to read that, it's undefined. You're not allowed to use the . operator on things that are undefined, so when you do .name on that, you get the error that you got.
You could also use a ternary operator {{ user.area ? user.area.name : "" }}

Why "Error in render: TypeError: Cannot read property 'filter' of undefined" returned even data already available?

I already initialize the data.
data () {
return {
current_product: {},
current_ID: '',
}
}
Then, I fetch data from a REST API on lifecycle created hook.
created () {
var skuID = this.$store.state.selected_productSKU.productSKU_ID
axios.get(`http://localhost:8081/api/products/${skuID}`)
.then(response => {
this.current_ID = response.data.product_ID
this.current_product = response.data
})
.catch(e => {
alert(e)
})
}
And finally, I use computed property to get some value
// THIS JUST RETURN ['XL', 'M']
focusedProduct_SKUS_NoDupSizes () {
var newArr = this.current_product.product_SKU.filter((sku, index, self) =>
index === self.findIndex(t => (
t.productSKU_size === sku.productSKU_size
))
)
var x = newArr.map(a => a.productSKU_size)
return x
}
The vue instance show expected result
But if i call {{ focusedProduct_SKUS_NoDupSizes }} in template.
It doesn't rendered.
The browser return error Error in render: "TypeError: Cannot read property 'filter' of undefined"
What is happening? My first guess is the computed property using the initial structure of current_product which is {} empty object. But isn't that how to initialize an object?
Because of:
computed:
// ...
focusedProduct_SKUS_NoDupSizes () {
var newArr = this.current_product.product_SKU.filter((sku, index, self) =>
^^^^^^^^^^^
You should initialize product_SKU with an empty array:
data () {
return {
current_product: {product_SKU: []}, // changed here
current_ID: '',
}
}
This is needed because the computed property will be executed right away, even before your Ajax gets a chance to return.
Declare it as empty so the computed doesn't throw an error. When the Ajax fulfills, it will recompute automatically.
Even though the Ajax is started at the created(), it won't return before the computed is executed for the first time. More details about this here.

Using $refs in a computed property

How do I access $refs inside computed? It's always undefined the first time the computed property is run.
Going to answer my own question here, I couldn't find a satisfactory answer anywhere else. Sometimes you just need access to a dom element to make some calculations. Hopefully this is helpful to others.
I had to trick Vue to update the computed property once the component was mounted.
Vue.component('my-component', {
data(){
return {
isMounted: false
}
},
computed:{
property(){
if(!this.isMounted)
return;
// this.$refs is available
}
},
mounted(){
this.isMounted = true;
}
})
I think it is important to quote the Vue js guide:
$refs are only populated after the component has been rendered, and they are not reactive. It is only meant as an escape hatch for direct child manipulation - you should avoid accessing $refs from within templates or computed properties.
It is therefore not something you're supposed to do, although you can always hack your way around it.
If you need the $refs after an v-if you could use the updated() hook.
<div v-if="myProp"></div>
updated() {
if (!this.myProp) return;
/// this.$refs is available
},
I just came with this same problem and realized that this is the type of situation that computed properties will not work.
According to the current documentation (https://v2.vuejs.org/v2/guide/computed.html):
"[...]Instead of a computed property, we can define the same function as a method. For the end result, the two approaches are indeed exactly the same. However, the difference is that computed properties are cached based on their reactive dependencies. A computed property will only re-evaluate when some of its reactive dependencies have changed"
So, what (probably) happen in these situations is that finishing the mounted lifecycle of the component and setting the refs doesn't count as a reactive change on the dependencies of the computed property.
For example, in my case I have a button that need to be disabled when there is no selected row in my ref table.
So, this code will not work:
<button :disabled="!anySelected">Test</button>
computed: {
anySelected () {
if (!this.$refs.table) return false
return this.$refs.table.selected.length > 0
}
}
What you can do is replace the computed property to a method, and that should work properly:
<button :disabled="!anySelected()">Test</button>
methods: {
anySelected () {
if (!this.$refs.table) return false
return this.$refs.table.selected.length > 0
}
}
For others users like me that need just pass some data to prop, I used data instead of computed
Vue.component('my-component', {
data(){
return {
myProp: null
}
},
mounted(){
this.myProp= 'hello'
//$refs is available
// this.myProp is reactive, bind will work to property
}
})
Use property binding if you want. :disabled prop is reactive in this case
<button :disabled="$refs.email ? $refs.email.$v.$invalid : true">Login</button>
But to check two fields i found no other way as dummy method:
<button :disabled="$refs.password ? checkIsValid($refs.email.$v.$invalid, $refs.password.$v.$invalid) : true">
{{data.submitButton.value}}
</button>
methods: {
checkIsValid(email, password) {
return email || password;
}
}
I was in a similar situation and I fixed it with:
data: () => {
return {
foo: null,
}, // data
And then you watch the variable:
watch: {
foo: function() {
if(this.$refs)
this.myVideo = this.$refs.webcam.$el;
return null;
},
} // watch
Notice the if that evaluates the existence of this.$refs and when it changes you get your data.
What I did is to store the references into a data property. Then, I populate this data attribute in mounted event.
data() {
return {
childComps: [] // reference to child comps
}
},
methods: {
// method to populate the data array
getChildComponent() {
var listComps = [];
if (this.$refs && this.$refs.childComps) {
this.$refs.childComps.forEach(comp => {
listComps.push(comp);
});
}
return this.childComps = listComps;
}
},
mounted() {
// Populates only when it is mounted
this.getChildComponent();
},
computed: {
propBasedOnComps() {
var total = 0;
// reference not to $refs but to data childComps array
this.childComps.forEach(comp => {
total += comp.compPropOrMethod;
});
return total;
}
}
Another approach is to avoid $refs completely and just subscribe to events from the child component.
It requires an explicit setter in the child component, but it is reactive and not dependent on mount timing.
Parent component:
<script>
{
data() {
return {
childFoo: null,
}
}
}
</script>
<template>
<div>
<Child #foo="childFoo = $event" />
<!-- reacts to the child foo property -->
{{ childFoo }}
</div>
</template>
Child component:
{
data() {
const data = {
foo: null,
}
this.$emit('foo', data)
return data
},
emits: ['foo'],
methods: {
setFoo(foo) {
this.foo = foo
this.$emit('foo', foo)
}
}
}
<!-- template that calls setFoo e.g. on click -->