Vue cannot read property of null while using v-for - javascript

Is there a way to solve an error which says id not defined on v-bind:key="persons.id" ?
My View
<div v-for="reservation in reservationNameByTime" v-bind:key="reservation.id">
{{reservation.id}} /** displays 1 **/
<div v-for="player in reservation.Players" v-bind:key="player.id">
{{player.id}} /**displays 1 **/
<div v-for="persons in player.Person" v-bind:key="persons.id"> /** throws an error as id of null **/
{{persons.name}}
</div>
</div>
</div>
JSON DATA
reservationNameByTime: [
{
id: 1, /** defines reservation id **/
Players: [
id: 1, /** defines players id **/
Person:{
id: 1, /** defines the person id **/
name: John
}
]
}
]
Image for array data

<div v-for="(reservation, i) in reservationNameByTime" v-bind:key="i">
{{reservation.id}} /** displays 1 **/
<div v-for="(player, j) in reservation.Players" v-bind:key="j">
{{player.id}} /**displays 1 **/
<div v-for="(persons, k) in player.Person" v-bind:key="k">
{{persons.name}}
</div>
</div>
</div>

Your data is malformed, try this with the html code in your post
reservationNameByTime: [{
id: 1,
Players: [{
id: 1,
Person: [{
id: 1,
name: 'John'
},
{
id: 2,
name: 'Marc'
}]
}]
}]
But this (below) is better, for each reservation, you have an id and a list of players, player have id and name
reservation: [{
id: 1,
players: [{
id: 21,
name: 'John'
},
{
id: 55,
name: 'Marc'
}]
},
{
id: 2,
players: [{
id: 34,
name: 'Adrien'
},
{
id: 12,
name: 'Marion'
}]
}]
HTML / VUE
<div v-for="reservation in reservations" v-bind:key="reservation.id">
{{reservation.id}}
<div v-for="player in reservation.players" v-bind:key="player.id">
{{player}}
</div>
</div>

player.Person is an object and v-for on an object iterates through the properties of the object and returns its values. In this case it would be 1 and John. So you're trying to get 1.id and John.id.
If you're only going to have one person, you could just do:
div v-bind:key="player.Person.id">
{{player.Person.name}}
</div>

Related

update complex array react js useState

I have a long array. In this array I want to update the qty which is under misc array
I have a list of a person, let's say person with index 0 and index 1, each person can have misc with index 0, and index 1 and each misc can have array with index 0 and 1 and I want to update the qty of misc array.
Here is an example: https://playcode.io/1028032
import React from 'react';
import { useState } from 'react';
export function App(props) {
const[persons,setPersons] = useState([
{
id: 1,
name: "john",
gender: "m",
misc: [
{
id: 1,
name: "xxx",
qty: 1
},
{
id: 2,
name: "xxx1",
qty: 1
}
]
},
{
id: 2,
name: "mary",
gender: "f",
misc: [
{
id: 1,
name: "aaa",
qty: 1
},
{
id: 2,
name: "bbb",
qty: 1
}
]
},
]
)
const updatePersonMiscQty = (personIndex, miscIndex) => {
setPersons(persons => {
const miscItem = persons[personIndex]?.misc?.[miscIndex]
if (miscItem ) {
miscItem.qty += 1;
}
return [...persons];
})
}
return (
<div className='App'>
<h1>Hello React.</h1>
<h2>Start editing to see some magic happen!</h2>
<a href="" onClick={()=>updatePersonMiscQty(0,0)}>Click</a>
{console.log(persons)}
</div>
);
}
Let's say I passed 0,0 in updatePersonMiscQty(), first 0 is personIndex, and second 0 is miscIndex. so now it should update qty of person with index 0 and misc with index 0. This array. But nothing is rendered.
{
id: 1,
name: "john",
gender: "m",
misc: [
{
id: 1,
name: "xxx",
qty: 2
},
This is reloading the page:
<a href="" onClick={()=>updatePersonMiscQty(0,0)}>Click</a>
You can prevent this by setting the href to "#".
<a href="#" onClick={()=>updatePersonMiscQty(0,0)}>Click</a>
Or you probably want to use a button instead.
<button onClick={()=>updatePersonMiscQty(0,0)}>Click</button>
you just need to change <a> tag to <button> but I also would recommend to create deeply nested copies when trying to update a nested object.
Try immer.js, with it you just specify what has changed at any level of nesting and it will automatically take care of all the changes that need to be handled.https://immerjs.github.io/immer/

How get a load more button for a li list vue js

I'm trying to implement a load more button to my code. I would be able to this in javascript but I can't find a similar way in vue.
This is my vue code. I've tried asking the element with the company id but it's not reactive so I can't just change the style.
<main>
<ul>
<li v-for="company in companiesdb" :key="company.id" v-bind:id="company.id" ref="{{company.id}}" style="display: none">
{{company.name}}<br>
{{company.email}}
</li>
</ul>
</main>
this is my failed atempt of doing it in javascript but as I've mentioned before ref is not reactive so I can't do it this way
limitView: function (){
const step = 3;
do{
this.numberCompaniesVis ++;
let li = this.$refs[this.numberCompaniesVis];
li.style = "display: block";
}while (this.numberCompaniesVis % 3 != step)
}
I think the way you are approaching this problem is a little complex. Instead, you can create a computed variable that will change the number of lists shown.
Here's the code
<template>
<div id="app">
<ul>
<li v-for="(company, index) in companiesLoaded" :key="index">
{{ company }}
</li>
</ul>
<button #click="loadMore">Load</button>
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
companiesdb: [3, 4, 1, 4, 1, 2, 4, 4, 1],
length: 5,
};
},
methods: {
loadMore() {
if (this.length > this.companiesdb.length) return;
this.length = this.length + 3;
},
},
computed: {
companiesLoaded() {
return this.companiesdb.slice(0, this.length);
},
},
};
</script>
So instead of loading the list from companiesdb, create a computed function which will return the new array based on companiesdb variable. Then there's the loadMore function which will be executed every time user clicks the button. This function will increase the initial length, so more lists will be shown.
Here's the live example
Just use computed property to create subset of main array...
const vm = new Vue({
el: '#app',
data() {
return {
companies: [
{ id: 1, name: "Company A" },
{ id: 2, name: "Company B" },
{ id: 3, name: "Company C" },
{ id: 4, name: "Company D" },
{ id: 5, name: "Company E" },
{ id: 6, name: "Company F" },
{ id: 7, name: "Company G" },
{ id: 8, name: "Company H" },
{ id: 9, name: "Company I" },
{ id: 10, name: "Company J" },
],
companiesVisible: 3,
step: 3,
}
},
computed: {
visibleCompanies() {
return this.companies.slice(0, this.companiesVisible)
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<ul>
<li v-for="company in visibleCompanies" :key="company.id" :id="company.id">
{{company.name}}
</li>
</ul>
<button #click="companiesVisible += step" v-if="companiesVisible < companies.length">Load more...</button>
</div>

Display nested files in children of element ui tree using vue

I am using element ui tree for my vue application. I am implementing 'File browser' type system for my application. In here, files are nested into children.While clicking on child node those nested files or docs will be displaying right side in different container. I am not able to iterate through children and display those files.
**Here is the mocked data :**
data:[{
id: 1,
name: ‘Project A’,
type: ‘folder’,
children: [{
id: 4,
name: 'Project A-1’,
type: ‘folder’,
files: [
{
id: 9,
pid: 4,
name: ‘file 3-A’,
type:’file’,
description: ‘wifi’,
country: ‘USA'
},
{
id: 10,
pid: 4,
name: ‘file 3-B’,
type:’file’,
description: ‘VPN’,
country: ‘USA'
}
]
}
]
},
{
id: 2,
name: 'Services’,
type: 'folder',
children:[],
files: [
{
id: 5,
name: ‘Services-1-A’,
type:’file’,
pid: 2,
description: ‘VPN’,
country: ‘AUS'
},
{
id: 6,
name: ‘Services-1-B’,
type:’file’,
pid: 2,
description: ‘WIFI’,
country: ‘AUS'
}
]
},
{
id: 3,
name: 'Servers',
type: 'folder’,
children:[],
files: [
{
id: 7,
name: ‘Servers-1-A’,
type: ‘file’,
pid: 3,
description: ‘VPN’,
country: ‘CAD'
},
{
id: 8,
name: ‘Servers-1-B',
type: ‘file’,
pid: 3,
description: ‘WIFI’,
country: ‘CAD'
}
]
}]
Here is my UI code
<el-row>
<el-col :span="8" style="background: #f2f2f2">
<div class="folder-content">
<el-tree
node-key="id"
:data="data"
accordion
#node-click="nodeclicked"
ref="tree"
style="background: #f2f2f2"
highlight-current
>
<span class="custom-tree-node" slot-scope="{ node, data }">
<span class="icon-folder">
<i class="el-icon-folder" aria-hidden="true"></i>
<span class="icon-folder_text" #click="showFiles(data.id)">{{ data.name }}</span>
</span>
</span>
</el-tree>
</div>
</el-col>
<el-col :span="16"><div class="entry-content">
<ul>
<li aria-expanded="false" v-for="(file,index) in files" :key="index">
<div class="folder__list"><input type="checkbox" :id= "file" :value="file" v-model="checkedFiles" #click="check">
<i class="el-icon-document" aria-hidden="true"></i>
<span class="folder__name">{{file}}</span></div>
</li>
</ul>
</div></el-col>
</el-row>
Show files method:
showFiles(id) {
let f = this.data.filter(dataObject => {
if (dataObject.children && dataObject.children.id === id) {
return false
} else if (!dataObject.children && dataObject.id === id) {
return false
}
return true
})[0]
this.files = f.files
}
}
I am trying to do like this:
I noticed a bug in your filter function. Check line 3 :
showFiles(id) {
let f = this.data.filter(dataObject => {
//isn't this suppose to return true?
if (dataObject.children && dataObject.children.id === id) {
return false
} else if (!dataObject.children && dataObject.id === id) {
return false
}
return true
})[0]
this.files = f.files
}
Why using filter() method to search for single element? It will scan through all the elements. You could just find() instead to improve performance and better readable code.
Try this:
showFiles(id) {
let f = this.data.find(dataObject => dataObject.id == id);
//ensure node was returned
if(f ){
this.files = f.files
}
}
However, You could try and do this in your component instead.
Add another property to the component's data object. Use the new property to hold the selected node.
data(){
//your mock data
tree:[],
//children files being displayed
files:[]
},
methods:{
showFiles(branch){
this.files = branch.files;
}
}
Then pass the whole object to the method
<span class="icon-folder_text" #click="showFiles(data)">{{ data.name }}</span>

Nested Vue components with counts of direct children and nested children

I am trying to implement nested comments in vue.js and nuxt.js.
Each comment can have one or more children comments.
Each child comment, can again, have one or more children comments.
Unlimited levels of nested comments is possible.
As you can see in the diagram I have attached, I would like each comment to "know" (for the sake of simplicity, to display) the following information:
The depth of the comment (I have this working already). Example, all of the "top-level" comments are at depth=0, all their children are at depth=1, and so on.
The number of direct children
the number of children (including nested children, unlimited levels deep)
I came across this question on StackOverflow but it doesn't quite do the trick. Or maybe I am doing something wrong.
In case you want to take a look at my (very messy) code, here it is. However, I'm willing to start over, so appreciate any pointers on how to pass the data up / down the chain of nested comments (vue components). Some sample code would be great.
components/PostComment.vue:
<template>
<div>
<div class="tw-flex tw-flex-wrap tw-justify-end">
<div :class="indent" class="tw-w-full tw-flex">
<div class="tw-font-bold tw-p-4 tw-border-gray-400 tw-border tw-rounded tw-text-right">
<div class="kb-card-section">
<div class="kb-card-section-content tw-flex tw-flex-wrap tw-items-center tw-text-left">
<div class="tw-flex tw-w-full">
<div class="tw-hidden md:tw-block md:tw-w-2/12 tw-text-right tw-my-auto">
<div class="tw-flex">
<p class="tw-w-full tw-text-xs tw-text-gray-600 tw-text-right">children: {{ numNestedChildComments }}, depth: {{depth}}</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="tw-w-full" v-if="commentData.nested_comments" v-for="nestedComment in commentData.nested_comments">
<post-comment
:commentData="nestedComment"
:depth="depth + 1"
:numChildCommentsOfParent=numNestedChildComments
/>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'post-comment', // necessary for recursive components / nested comments to work
props: {
depth: {
type: Number,
required: true
},
postAuthorData: {
type: Object,
required: true
},
commentAuthorData: {
type: Object,
required: true
},
commentData: {
type: Object,
required: true
},
numChildCommentsOfParent: {
type: Number,
required: true
},
},
data() {
return {
numNestedChildComments: this.numChildCommentsOfParent,
}
},
mounted() {
this.incrementNumParentComments();
},
methods: {
incrementNumParentComments() {
this.numNestedChildComments++;
this.$emit('incrementNumParentComments');
},
},
computed: {
indent() {
switch (this.depth) {
case 0:
return "tw-ml-0 tw-mt-1";
case 1:
return "tw-ml-4 tw-mt-1";
case 2:
return "tw-ml-8 tw-mt-1";
case 3:
default:
return "tw-ml-12 tw-mt-1";
}
},
},
}
</script>
Figured it out with some help from Rodrigo Pedra from the Laracasts community.
Here as a parent component calling the tree roots:
<template>
<div>
<MyTree v-for="item in records" :key="item.id" :item="item" />
</div>
</template>
<script>
import MyTree from './MyTree';
const FIXTURE = [
{
id: 1,
children: [
{
id: 2,
children: [{id: 3}, {id: 4}, {id: 5}],
},
{
id: 6,
children: [
{id: 7},
{id: 8, children: [{id: 9}, {id: 10}]},
],
},
],
},
{
id: 11,
children: [
{id: 12, children: [{id: 13}, {id: 14}, {id: 15}]},
{id: 16, children: [{id: 17}]},
{id: 18},
],
},
];
export default {
components: {MyTree},
data() {
return {
records: FIXTURE,
};
},
};
</script>
And here is the tree component:
<template>
<div>
<div style="border: 1px solid black; padding: 5px;" :style="offset">
id: {{ item.id }}
// depth: {{ depth }}
// direct: {{ direct }}
// children: {{ childrenCount }}
</div>
<template v-if="item.children">
<MyTree
v-for="record in item.children"
:key="record.id"
:item="record"
:depth="depth + 1"
#born="handleBorn()" />
</template>
</div>
</template>
<script>
const COLORS = [
'white',
'lightgray',
'lightblue',
'lightcyan',
'lightskyblue',
'lightpink',
];
export default {
// MUST give a name in recursive components
// https://vuejs.org/v2/guide/components-edge-cases.html#Recursive-Components
name: 'MyTree',
props: {
item: {type: Object, required: true},
depth: {type: Number, default: 0},
},
data() {
return {
childrenCount: 0,
};
},
computed: {
direct() {
if (Array.isArray(this.item.children)) {
return this.item.children.length;
}
return 0;
},
offset() {
return {
'margin-left': (this.depth * 20) + 'px',
'background-color': COLORS[this.depth % COLORS.length],
};
},
},
mounted() {
this.$emit('born');
},
methods: {
handleBorn() {
this.childrenCount++;
this.$emit('born');
},
},
};
</script>
Screenshot:

Kendo ui MVVM - How to bind an observable array and array inside array using kendo templates

Here is the my working DEMO.
I have an observable array named persons and each array element contains an another array named hobbies.
I have successfully bound the persons array using kendo template, but does anyone know how should I bind the hobbies array using an another template. Below is the code from my DEMO.
Code:
var persons = new kendo.data.ObservableArray(
[
{
name: "John Doe",
age: 28,
hobbies: [
{ id: 1, description: "Baseball", rank: 1 },
{id: 2, description: "music", rank: 3 },
{ id: 3, description: "Surfing the web", rank: 2}
]
},
{
name: "Jane Doe",
age: 24,
hobbies: [
{ id: 1, description: "Volley Ball", rank: 1 },
{id: 2, description: "Cricket", rank: 3 },
{ id: 3, description: "Hockey", rank: 2}
]
}
]
);
var viewModel = kendo.observable({
array: persons
});
kendo.bind($("#example"), viewModel);
<h2>Persons Array</h2><br/>
<div id="example" data-template="template" data-bind="source: array">
</div>
<script id="template" type="text/x-kendo-template">
<div>
Name: #=name# || Age: #=age# <br>
<ul>Hobbies (below, I want to display hobbies)</ul>
<br/>
</div>
</script>
You need to use a for loop inside the ul tag, something like:
# for (var i = 0; i < hobbies.length; i++) { #
<li>#= hobbies[i].description#</li>
# } #
Here it is the updated fiddle
Use another nested template named "hobby-template" and bind "hobbies" as source to that
<h2>Persons Array</h2><br/>
<div id="example" data-template="template" data-bind="source: array">
</div>
<script id="template" type="text/x-kendo-template">
<div>
<span data-bind="text:name"></span>
<span data-bind="text:age"></span>
<ul data-template="hobby-template" data-bind="source: hobbies"></ul>
</div>
</script>
<script id="hobby-template" type="text/x-kendo-template">
<li>
<span data-bind="text:description"></span>
<span data-bind="text:rank"></span>
<li>
</script>

Categories

Resources