Vue Component Conditional Creation - javascript

I want to create a list of actions (each of which is a component) conditionally if the object variable store.plan is not empty, I have tried v-if which works well for rendering but not for creating the component.
I get an error:
Uncaught (in promise) TypeError: action is undefined
The full code of this component can be found here.
Can you please tell me how can I handle this problem? thanks in advance.
<template>
<div class="planlist" v-if="parse">
<ul id="planOl">
<Action
v-for="action in store.plan"
:action_id="action.act_id"
:actor="action.actor"
:color="action.color"
:size="action.size"
:lego_name="action.lego"
:pick_pos="action.pick"
:place_pos="action.place"
:blocked="action.blocked"
:status="action.status"
:key="action.act_id"
/>
</ul>
</div>
</template>
<script>
import Action from '../components/Action.vue';
import { store } from '../js/store.js'
export default {
name: 'Plan',
data() {
return {
store,
}
},
computed: {
parse() {
if (store.plan.length > 0) {
return true;
}
return false;
}
},
components: {Action}
}
</script>

Did you try with optional chaining:
parse() {
return store?.plan?.length > 0 ? true : false
}
and don't mix v-if and v-for. Try to create wrapper div with v-if around your component:
<ul id="planOl">
<div v-if="parse">
<Action
v-for="action in store.plan"
:action_id="action.act_id"
:actor="action.actor"
:color="action.color"
:size="action.size"
:lego_name="action.lego"
:pick_pos="action.pick"
:place_pos="action.place"
:blocked="action.blocked"
:status="action.status"
:key="action.act_id"
/>
</div>
</ul>

It is recommended not to use v-if and v-for directives together on the same element due to the syntax ambiguity.
As per your code, Computed property parse is used to check the length of an array. You can move the v-if to a container element (e.g. ul).
In template :
<ul id="planOl" v-if="parse">
<Action v-for="action in store.plan">...</Action>
</ul>
Script :
computed: {
parse() {
return store.plan.length > 0 ? true : false;
}
}

To make the process easy, we can move the store.plan to a computed property to use inside the template and parse property.
Simply return store.plan.length from the computed property will do the job too instead of returning true and false based on condition.
If you want to use v-if just outside the Action component, you can use template to do this. No need for an extra element.
So, below changes can help fixing the issues-
<template>
<div class="planlist">
<ul id="planOl">
<template v-if="parse">
<Action
v-for="action in plan"
:key="action.act_id"
:action_id="action.act_id"
:actor="action.actor"
:color="action.color"
:size="action.size"
:lego_name="action.lego"
:pick_pos="action.pick"
:place_pos="action.place"
:blocked="action.blocked"
:status="action.status"
/>
</template>
</ul>
</div>
</template>
<script>
import Action from "../components/Action.vue";
import { store } from "../js/store.js";
export default {
name: "Plan",
components: {
Action,
},
computed: {
// A computed property to access the plan ()
plan() {
return store.plan;
},
parse() {
/**
* 1. The plan should be available (not null or empty or undefined)
* 2. The plan should be an array so length property can be applied
* 3. If its an array then it should have data (length in other words)
*/
return this.plan && Array.isArray(this.plan) && this.plan.length;
},
},
};
</script>

Related

Need to understand VUE Property or method "range" is not defined on the instance but referenced during render

I need help understanding why the error would show. An example is when I use vue2-daterange-picker.
<date-range-picker
:singleDatePicker="range"
>
</date-range-picker>
So singleDatePicker is a prop correct? Why is it if I pass the value into the component this way, it returns the error, but if I add the value, range into data it doesn't? eg
<template>
<date-range-picker
:singleDatePicker="singleDatePicker"
>
</date-range-picker>
</template>
<script>
export default {
components: {
DateRangePicker
},
data () {
return {
singleDatePicker: "range",
}
},
}
</script>
With : (it's the same as v-bind) you are binding value, and range is not defined, so if you want to put data directly in your prop singleDatePicker="range"

Wait for definition before calling function - Vue.js

When it comes to creating methods in child components I'm having a hard time figuring a particular feature out.
I have this parent route/component (League.vue):
In this league.vue I render a child component:
<router-view :league="league" />
Child component:
<template>
<div v-if="teams_present">
<div class="page-container__table">
<h3 class="page-container__table__header">Teams</h3>
</div>
</div>
</template>
<script>
export default {
name: 'LeagueTeams',
props: [
'league'
],
data () {
},
computed: {
teams_present: function () {
return this.league.teams.length > 0
}
}
}
</script>
ERROR:
"TypeError: Cannot read property 'length' of undefined"
So it appears that the computed callback is called before the prop can be set, I think? and if a change it to methods it never gets called. How do I handle this case?
As Ali suggested, you can return this.league.teams && this.league.teams.length > 0, which definitely will work.
However, as my experience, to avoid these situation, and for good practice, always declare the type of the Props. So in your props:
export default {
name: 'LeagueTeams',
props: {
league: {
type: Object, // type validation Object
default() { return {teams: [] }} // add a default empty state for team, you can add more
}
},
data () {
},
computed: {
teams_present: function () {
return this.league.teams.length > 0 // now the old code should work
}
}
}
</script>
By doing this, you don't need to care much about checking the edge case of this.league.teams every time, since you may need to call it again in methods or in the <template> html
Update: Another suggestion is if you are using vue-cli 4, you can use Optional chaining and nullish coalescing.
return this.league?.teams.length ?? false // replace with only this line will work
Hope this will help you 2 more ways to deal with in these situations, and depends on situations you can choose the most suitable one

'v-bind' directives require an attribute value

I am trying to create some type of tree with vue.js and stuck on a problem with element props. Help me out plz.
I've tried :content="{{tempCont}}" and I've tried content="{{tempCont}}", but none of them worked.
Here's the place where I am using tree element:
<div id="tree">
<treeItem :title="Parent" :content="{{tempCont}}"></treeItem>
</div>
Here's the entire tree element:
<template>
<div>
<p v-on:click="openTree">{{title}}</p>
<div id="childs" v-if="childVisibility">
<treeItem v-for="item in content" :key="item" title=item>
</div>
</div>
</template>
<script>
export default {
data: {
childVisibility: false
},
methods: {
openTree: function(){
childVisibility = !childVisibility;
}
},
props: {
title: String,
content: Array,
}
}
</script>
<style scoped>
</style>
I am getting this error:
Use like this: :content="tempCont"
<div id="tree">
<treeItem :title="Parent" :content="tempCont"></treeItem>
</div>
Ok so first of all, when you v-bind something like v-bind:title or :title, what you bind is expressed as a javascript expression.
So if you want your title attribute to be the string Parent, you need either to write it like a native html attribute title="Parent" (notice the lack of :), or as a vue bound attribute v-bind:title="'Parent'" or :title="'Parent'" (notice the use of '' to express a string primitive type in javascript.
Now, the {{ variable }} syntax is used inside vuejs template but you do not need to use it inside v-bind attributes since they are already interpreted as javascript.
So you shouldn't write this:
<div id="tree">
<treeItem :title="Parent" :content="{{tempCont}}"></treeItem>
</div>
but this instead:
<div id="tree">
<treeItem title="Parent" :content="tempCont"></treeItem>
</div>
SincetempCont is already a valid javascript expression.
You don't really need {{}} for passing attributes.
<treeItem :title="Parent" :content="tempCont"></treeItem>
This shall be good enough to work. The puspose of {{}} is to print data and not pass attributes.
Also, in your tree component, it's a good practice to follow object notations in your props. For ex:
props: {
title: {
type: String
},
content: {
type: Array
},
}
Also you should make your components data reactive and making sure that childVisibility is set to this instance rather than a direct reference by setting it like this
export default {
data() {
return {
childVisibility: false
}
},
methods: {
openTree() {
this.childVisibility = !this.childVisibility;
}
},
props: {
title: String,
content: Array,
}
}

Use prop value as a variable in VueJS

I have a problem about using prop value as a variable in VueJS. I have a component which I tranmit prop:
This is parent component:
<template>
<div class="a">
<UploadAvatarModal
apiurl="upload_avatar"
id="UploadAvatarModal"
/>
</div>
</template>
This is script of UploadAvatarModal component:
<template>
<div class="a">
...
</div>
</template>
<script>
export default {
props: {
id: String,
apiurl: String
},
methods: {
def: function () {
this.$refs.id.hide()
}
}
}
</script>
In this line: this.$refs.id.hide() How can I call methods according to prop id. Example: this.$refs.UploadAvatarModal.hide() or this.$refs.UploadAvatarModal2.hide() changed by props value??
You can access props doing :
this.propName
To access id prop you need to do :
this.id
So the line you wrote this.$refs.id.hide() should be written :
this.$refs[this.id].hide()
But it will probably do nothing as .hide() is a jquery function.
In plain javascript you would need to do :
this.$refs[this.id].style.display = 'none'
That said, it's might not be a good idea to do so.
Using vue, the best way to show/hide a component is probably to use v-if or v-show

vue.js list ( template ) binding not updating when changing data from directive

First of all : I'm using laravel spark and the given setup of vue that comes with spark.
I have a "home" component with the prop "custom". Within custom there's a "passwords" array. (Entry added by code of directive, it's initialized empty)
My component ( alist) which should be bound against the data
<template id="passwords-list-template">
<div class="password" v-for="password in list">
<ul>
<li>{{ password.name }}</li>
<li>{{ password.description }}</li>
</ul>
</div>
</template>
<script>
export default {
template: '#passwords-list-template',
props: ['list'],
};
</script>
Usage
<passwords-list :list="custom.passwords"></passwords-list>
Using vue devtools I can see that my data is updating, however my list is not. Also other bindings like
<div v-show="custom.passwords.length > 0">
Are not working ...
UPDATE : Parent component (Home)
Vue.component('home', {
props: ['user', 'custom'],
ready : function() {
}
});
Usage
<home :user="user" :custom="spark.custom" inline-template>
Update 2: I played around a little bit using jsfiddle. It seems like changing the bound data object using $root works fine for me when using a method of a component. However it does not work when trying to access it using a directive
https://jsfiddle.net/wa21yho2/1/
There were a lot of errors in your Vue code. First of all, your components where isolated, there wasn't an explicit parent-child relationship.Second, there were errors in the scope of components, you were trying to set data of the parent in the child, also, you were trying to set the value of a prop, and props are by default readonly, you should have written a setter function or change them to data. And finally, I can't understand why were you trying to use a directive if there were methods and events involve?
Anyway, I rewrote your jsfiddle, I hope that you find what you need there. The chain is Root > Home > PasswordList. And the data is in the root but modified in home, the last component only show it. the key here are twoWay properties, otherwise you wouldn't be able to modify data through properties.
Here is a snippet of code
Home
var Home = Vue.component('home', {
props: {
user: {
default: ''
},
custom: {
twoWay: true
}
},
components: {
passwordList: PasswordList
},
methods: {
reset: function () {
this.custom.passwords = [];
}
}
});
// template
<home :custom.sync="spark.custom" inline-template>
{{custom | json}}
<button #click="reset">
reset in home
</button>
<password-list :list="custom.passwords"></password-list>
<password-list :list="custom.passwords"></password-list>
</home>
Here is the full jsfiddle

Categories