How to defined a array list in props and data - vue.js

In my project, I use vue.js.
I want to display content of list with nested loop。 In parent page, i have defined:
<template>
<div>
<detail-header></detail-header>
......
<detail-list></detail-list>
</div>
</template>
The component of detail-list is :
<template>
<div>
<div v-for="(item, index) of list" :key="index">
<div class="item-title border-bottom">
<span class="item-title-icon"></span>
{{item.title}}
</div>
<div v-if="item.children" class="item-children">
<detail-list :list="item.children"></detail-list>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'DetailList',
props: {
list: Array
},
data () {
return {
list: [{
title: 'adult',
children: [{title: 'threePeople',children: [{ title: 'threePeople-w'}]}, {title: 'fivePeople'}]
}, {
title: 'student'
}, {
title: 'child'
}, {
title: 'offer'
}]
}
}
}
</script>
unlucky, I got a error message:
Duplicated key 'list' of list: [{ in detail-list
who can help me ?

If you want this to work, keep the list in props (and remove it from DetailList's data) and define in your parent page's data.
So the first DetailList and its children will have the list as a prop.
So you'll have in the parent page :
<template>
<div>
<detail-header></detail-header>
......
<detail-list :list="list"></detail-list>
</div>
</template>
<script>
export default {
name: 'Parent',
data () {
return {
list: [{ ... the list ... }]
}
}

Related

Property or method "posts" is not defined on the instance but referenced during render

I am trying to replicate the vue tutorial example (found here: https://v3.vuejs.org/guide/component-basics.html#passing-data-to-child-components-with-props ), but with no success. Below is my code. I have a view called TA.vue, that i would like to import the component into and render. Any ideas what I am doing wrong?
TA.vue (the view):
<template id="front">
<b-container style="margin-top: 9rem;">
<b-row>
<div id="blog-posts-events-demo" class="demo">
<div>
<blog_post
v-for="post in posts"
:key="post.id"
:title="post.title"
></blog_post>
</div>
</div>
</b-row>
</b-container>
</template>
<script>
import blog_post from '../components/blog_post' // import the Header component
export default {
name: 'talent-acquisition',
components: {
blog_post
}
}
</script>
blog_post component:
Vue.component('blog_post', {
el: '#front',
data() {
return {
posts: [
{ id: 1, title: 'My journey with Vue'},
{ id: 2, title: 'Blogging with Vue'},
{ id: 3, title: 'Why Vue is so fun'}
]
}
},
props: ['title'],
template: `
<div class="blog_post">
<h4>{{ title }}</h4>
</div>
`
})
app.mount('#blog-posts-events-demo')
EDIT:
After following Amaarrockz suggestion, I have the following error:
vue.runtime.esm.js?2b0e:619 [Vue warn]: Failed to mount component: template or render function not defined.
found in
---> <BlogPost> at src/components/blog_post.vue
<TalentAcquisition>
<App> at src/App.vue
<Root>
The thing is you have array 'posts' defined in your child component('blog_post') which you are trying to iterate from the parent.
The better implementation would be to define the array in the parent component .
Please find the modifications below
TA.vue
<template id="front">
<b-container style="margin-top: 9rem;">
<b-row>
<div id="blog-posts-events-demo" class="demo">
<div>
<blog_post
v-for="post in posts"
:key="post.id"
:title="post.title"
></blog_post>
</div>
</div>
</b-row>
</b-container>
</template>
<script>
import blog_post from '../components/blog_post' // import the Header component
export default {
name: 'talent-acquisition',
components: {
blog_post
},
data() {
return {
posts: [
{ id: 1, title: 'My journey with Vue'},
{ id: 2, title: 'Blogging with Vue'},
{ id: 3, title: 'Why Vue is so fun'}
]
}
},
}
</script>
blog_post component:
Vue.component('blog_post', {
el: '#front',
props: ['title'],
template: `
<div class="blog_post">
<h4>{{ title }}</h4>
</div>
`
})
app.mount('#blog-posts-events-demo')

Can I pass props to a child component and use the data in the parent component in Vue

I would like to pass data to a child component and render that same data in the parent components. Also I would like to use the data in a function and not simply render it in the child. When I pass the props in this example it no longer renders the tags with the data
<template>
<div class="hello">
<div v-for="(section, index) in sections" :key="index">
<p>{{section.name}}</p>
<p>{{section.type}}</p>
</div>
</div>
</template>
<script>
export default {
name: "HelloWorld",
data() {
return {
sections: [
{
name: "scoop",
type: "boulder"
},
{
name: "pew pew",
type: "roped"
}
]
};
},
props: ["sections"]
};
</script>
You can't use the same word/property name (sections in your case) for data and props.
I assume your code is the parent component:
// parent.vue
<template>
<div class="hello">
<div v-for="(section, index) in sections" :key="index">
<p>{{section.name}}</p>
<p>{{section.type}}</p>
</div>
<my-child-component :sections="sections" />
</div>
</template>
<script>
import MyChildComponent from '~/components/MyChildComponent.vue'
export default {
name: "HelloWorld",
components: {
MyChildComponent
},
data() {
return {
sections: [
{
name: "scoop",
type: "boulder"
},
{
name: "pew pew",
type: "roped"
}
]
}
},
methods: {
consoleSections() {
console.log(this.sections) // the way to use data in function
}
}
}
</script>
// MyChildComponent.vue
<template>
<div class="hello">
<div v-for="(section, index) in sections" :key="index">
<p>{{section.name}}</p>
<p>{{section.type}}</p>
</div>
</div>
</template>
<script>
export default {
name: "ChildHelloWorld",
props: ['sections'],
methods: {
consoleSections() {
console.log(this.sections) // the way to use data in child
}
}
}
</script>
Take a look in the vue guide about component: https://v2.vuejs.org/v2/guide/components.html

Send data from one component to another in vue

Hi I'm trying to send data from one component to another but not sure how to approach it.
I've got one component that loops through an array of items and displays them. Then I have another component that contains a form/input and this should submit the data to the array in the other component.
I'm not sure on what I should be doing to send the date to the other component any help would be great.
Component to loop through items
<template>
<div class="container-flex">
<div class="entries">
<div class="entries__header">
<div class="entries__header__title">
<p>Name</p>
</div>
</div>
<div class="entries__content">
<ul class="entries__content__list">
<li v-for="entry in entries">
{{ entry.name }}
</li>
</ul>
</div>
<add-entry />
</div>
</div>
</template>
<script>
import addEntry from '#/components/add-entry.vue'
export default {
name: 'entry-list',
components: {
addEntry
},
data: function() {
return {
entries: [
{
name: 'Paul'
},
{
name: 'Barry'
},
{
name: 'Craig'
},
{
name: 'Zoe'
}
]
}
}
}
</script>
Component for adding / sending data
<template>
<div
class="entry-add"
v-bind:class="{ 'entry-add--open': addEntryIsOpen }">
<input
type="text"
name="addEntry"
#keyup.enter="addEntries"
v-model="newEntries">
</input>
<button #click="addEntries">Add Entries</button>
<div
class="entry-add__btn"
v-on:click="openAddEntry">
<span>+</span>
</div>
</div>
</template>
<script>
export default {
name: 'add-entry',
data: function() {
return {
addEntryIsOpen: false,
newEntries: ''
}
},
methods: {
addEntries: function() {
this.entries.push(this.newEntries);
this.newEntries = '';
},
openAddEntry() {
this.addEntryIsOpen = !this.addEntryIsOpen;
}
}
}
</script>
Sync the property between the 2:
<add-entry :entries.sync="entries"/>
Add it as a prop to the add-entry component:
props: ['entries']
Then do a shallow merge of the 2 and emit it back to the parent:
this.$emit('entries:update', [].concat(this.entries, this.newEntries))
(This was a comment but became to big :D)
Is there a way to pass in the key of name? The entry gets added but doesn't display because im looping and outputting {{ entry.name }}
That's happening probably because when you pass "complex objects" through parameters, the embed objects/collections are being seen as observable objects, even if you sync the properties, when the component is mounted, only loads first level data, in your case, the objects inside the array, this is performance friendly but sometimes a bit annoying, you have two options, the first one is to declare a computed property which returns the property passed from the parent controller, or secondly (dirty and ugly but works) is to JSON.stringify the collection passed and then JSON.parse to convert it back to an object without the observable properties.
Hope this helps you in any way.
Cheers.
So with help from #Ohgodwhy I managed to get it working. I'm not sure if it's the right way but it does seem to work without errors. Please add a better solution if there is one and I'll mark that as the answer.
I follow what Ohmygod said but the this.$emit('entries:update', [].concat(this.entries, this.newEntries)) didn't work. Well I never even need to add it.
This is my add-entry.vue component
<template>
<div
class="add-entry"
v-bind:class="{ 'add-entry--open': addEntryIsOpen }">
<input
class="add-entry__input"
type="text"
name="addEntry"
placeholder="Add Entry"
#keyup.enter="addEntries"
v-model="newEntries"
/>
<button
class="add-entry__btn"
#click="addEntries">Add</button>
</div>
</template>
<script>
export default {
name: 'add-entry',
props: ['entries'],
data: function() {
return {
addEntryIsOpen: false,
newEntries: ''
}
},
methods: {
addEntries: function() {
this.entries.push({name:this.newEntries});
this.newEntries = '';
}
}
}
</script>
And my list-entries.vue component
<template>
<div class="container-flex">
<div class="wrapper">
<div class="entries">
<div class="entries__header">
<div class="entries__header__title">
<p>Competition Entries</p>
</div>
<div class="entries__header__search">
<input
type="text"
name="Search"
class="input input--search"
placeholder="Search..."
v-model="search">
</div>
</div>
<div class="entries__content">
<ul class="entries__content__list">
<li v-for="entry in filteredEntries">
{{ entry.name }}
</li>
</ul>
</div>
<add-entry :entries.sync="entries"/>
</div>
</div>
</div>
</template>
<script>
import addEntry from '#/components/add-entry.vue'
import pickWinner from '#/components/pick-winner.vue'
export default {
name: 'entry-list',
components: {
addEntry,
pickWinner
},
data: function() {
return {
search: '',
entries: [
{
name: 'Geoff'
},
{
name: 'Stu'
},
{
name: 'Craig'
},
{
name: 'Mark'
},
{
name: 'Zoe'
}
]
}
},
computed: {
filteredEntries() {
if(this.search === '') return this.entries
return this.entries.filter(entry => {
return entry.name.toLowerCase().includes(this.search.toLowerCase())
})
}
}
}
</script>

Vue: data from grandparent to grandchild (search component)

In my Vue app I have a view ('projects.vue') that gets some .json and which has a child component ('subheader.vue') which imports a search/filter mixin. I had this working but I wanted to split out the search elements from the subheader component to its own component, so subheader would hold only the headings and then import the search component. Despite adapting the props and bindings from the working version, my new three-component setup is throwing an error. Here's the setup:
the searchMixin.js:
export default {
computed: {
filteredProjects: function() {
const searchTerm = this.search.toLowerCase();
if (!searchTerm) {
return false;
}
return this.projects.filter((project) => {
return (project.client.toLowerCase().match(searchTerm)) ||
(project.contacts.filter((el) => {
return el.name.toLowerCase().match(searchTerm);
}).length > 0) ||
(project.projectReference.toLowerCase().match(searchTerm));
});
}
}
}
Projects.vue:
<template>
<div class="container" id="projects">
<!-- app-subheader is global component, imported & registered in main.js -->
<app-subheader v-bind:title="title" v-bind:subtitle="subtitle" />
[ snip irrelevant stuff ]
</div>
</template>
<script>
export default {
data () {
return {
title: "Projects",
subtitle: "",
projects: []
}
},
created: function() {
this.$http.get('https://xyz.firebaseio.com/projects.json')
.then(function(data){
return data.json();
})
.then(function(data){
var projectsArray = [];
for (let key in data) {
data[key].id = key;
this.projectID = key;
projectsArray.push(data[key]);
}
this.projects = projectsArray;
})
},
} // export default
</script>
subheader.vue:
<template>
<div class="subheader">
<div class="headings">
<h1 v-html="title"></h1>
<h2 v-html="subtitle"></h2>
<!-- I want to conditionally include <app-search /> according to an ID on an element in the grandparent (e.g., projects.vue) -->
<app-search v-bind:projects="projects" />
</div>
</div>
</template>
<script>
import Search from './search.vue';
export default {
components: {
'app-search': Search
},
props: [ "title", "subtitle", "projects" ],
data() {
return {
search: "",
projects: []
}
},
created: function() {
console.log("created; log projects", this.projects);
},
methods: {},
computed: {}
}
</script>
search.vue:
<template>
<div class="search-wrapper">
<div class="search">
<input type="text" v-model="search" placeholder="search by client, contact name, description, project, source" />
</div>
<div class="search-results-wrapper">
<h3>search-results:</h3>
<span class="results-count" v-if="filteredProjects.length == 0">
no results matching search "{{ search }}":
</span>
<span class="results-count" v-if="filteredProjects.length > 0">
{{ filteredProjects.length }} result<span v-if="filteredProjects.length > 1">s</span> matching search "{{ search }}":
</span>
<ul class="search-results" v-bind:class="{ open: filteredProjects.length > 0 }">
<li v-for="(project, ix) in filteredProjects" :key="ix">
{{ ix + 1 }}:
<router-link v-bind:to="'/project-detail/' + project.id">
{{ project.client }} ({{ project.projectReference }})
</router-link>
</li>
</ul>
</div>
</div><!-- END .search-wrapper -->
</template>
<script>
import searchMixin from '../mixins/searchMixin.js';
export default {
props: [ "projects" ],
data() {
return {
search: "",
}
},
created: function() {
},
mixins: [ searchMixin ],
}
When the search function is invoked, this error is thrown:
[Vue warn]: Error in render: "TypeError: Cannot read property 'filter' of undefined"
found in --->
<AppSearch> at src/components/search.vue
<AppSubheader> at src/components/subheader.vue
<Projects> at src/components/projects.vue
<App> at src/App.vue
... which seems to suggest the search mixin is not getting 'projects'.
Also, in subheader.vue, I get various errors whether I have 'projects' as a prop or 'projects: []' as a data key, and in once case or another I either get no results from the search function or an error, "Property or method "projects" is not defined on the instance but referenced during render".
Obviously I'm lacking clarity on the docs re; grandparent-parent-child data flow. Any help is greatly appreciated.
Whiskey T.

Using a vue-apollo component with different queries on the same page

I would like to make a wrapper component using Apollo, which receives different GraphQL queries and variables within props, queries GraphQL source and and passes response to its child component. I would like to use this component twice on a page with different queries.
But I think I'm stuck at some point. I end up having both instances of the component working with same inputs: query prop of the first dropdown component is being used in all dropdowns on the page. Although they have a different scope, different keywords and items, all dropdowns on the page are using the query of first dropdown.
Here is my DropdownSearch component. It passes keyword from searchbar component to query-list component:
<template>
<div class="dropdown-search">
<div class="dropdown-search-display" #click="toggleDropdown">
<span>{{value[nameProp]}}</span>
<span class="dropdown-search-dropdown-toggle">
<i v-if="!isOpen" class="fa fa-caret-down" aria-hidden="true"></i>
<i v-if="isOpen" class="fa fa-caret-up" aria-hidden="true"></i>
</span>
</div>
<div :class="{ 'dropdown-search-dropdown': true, 'dropdown-search-dropdown--open': isOpen}" >
<search-bar v-model="searchKeyword" class="dropdown-search-searchbar"></search-bar>
<div class="dropdown-search-list">
<query-list
:query="query"
:keyword="searchKeyword"
:listItemComponent="ListItem"
></query-list>
</div>
</div>
</div>
</template>
<script>
const ListItem = {
template: '<div class="dropdown-search-list-item">{{record.name}}</div>',
props: {
record: { required: true },
},
};
export default {
name: 'dropdownSearch',
props: {
name: String,
nameProp: String,
value: Object,
query: Object,
},
components: {
'dropdown-list-item': ListItem,
},
data() {
return {
isOpen: false,
searchKeyword: '',
ListItem,
};
},
methods: {
toggleDropdown() {
this.isOpen = !this.isOpen;
},
},
};
</script>
QueryList component, which is using Apollo to make queries and displays a list of results:
<template>
<div class="">
<loading-indicator :isLoading="loading > 0"></loading-indicator>
<ul class="app__list">
<li class="app__list-item" v-for="item in items" :key="item.id">
<component
:is="listItemComponent"
:record="item"
></component>
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'queryList',
props: {
query: Object,
keyword: String,
listItemComponent: { required: true },
},
data() {
return {
items: [],
loading: 0,
};
},
apollo: {
items: {
query() {
return this.query;
},
variables() {
return {
keyword: this.keyword || '',
};
},
loadingKey: 'loading',
},
},
};
</script>
This is how I use Dropdown Search components on a page:
<label class="edit-record-field">
<span class="edit-record-field-label">Category</span>
<dropdown-search
class="edit-record-field-input"
v-model="record.category"
:nameProp="'name'"
:query="getCategories"
></dropdown-search>
</label>
<label class="edit-record-field">
<span class="edit-record-field-label">Location</span>
<dropdown-search
class="edit-record-field-input"
v-model="record.location"
:nameProp="'name'"
:query="getLocation"
></dropdown-search>
</label>
I'm looking for a solution. I would be grateful if you could help me to make my components working.