I have 2 components:
Vue.component('page', {
props: ['pages'],
template: '<li>{{ pages.title }}</li>'
})
Vue.component('block', {
props: ['blocks'],
template: '<div>{{ blocks.id }}</div>'
})
"Page" is the Parent component, and "Block" the child component.
Both of the components contain a loop through an array:
<page v-for="page in pages" v-bind:pages="page">
<block v-for="block in blocks" v-bind:blocks="block"></block>
</page>
The "Block" component is not being rendered at all, while it is when placed outside of the parent "Page" component. I feel like I am missing something obvious, but what is it?
You should include the block component in the list of components for the page component.
var block = {
props: ['blocks'],
template: '<div>{{ blocks.id }}</div>'
});
Vue.component('page', {
data: function () {
return {
pages: [{title: 'Some title', blocks: [{id: 1, {id: 2}, {id: 3]}]
}
},
components: {
block: block,
},
props: ['pages'],
template: `<li>{{ pages.title }}<block v-for="block in page.blocks" v-bind:blocks="block"></block></li>`
});
Then use the page component like:
<page v-for="page in pages" v-bind:pages="page"></page>
You can check out the documentation
Related
I'm trying to pass an array of object to a childComponent as prop but when I add an object in it, it doesn't render. (Note: I'm working on vuejs 2.6)
I suppose it has a link with the "monitoring" of the items of the array and not the array itself? Stuff is that if I do not pass the prop and use the default value instead, it's working perfectly. I think I'm missing something here. Could someone help me ?
By curiosity is this kind of behavior still stand with vue3js ?
As you can see below:
App.vue:
<template>
<div id="app">
<Card
v-for="user in users"
:key="user.userId"
:userId="user.userId"
:username="getUsernameFromUserId(user.userId)"
:links="getUserLinksFromUserId(user.userId)"
/>
</div>
</template>
<script>
import Card from "./components/Card.vue";
export default {
name: "App",
components: {
Card,
},
data: function () {
return {
users: [
{ userId: 1, name: "Bob" },
{ userId: 2, name: "Alice" },
{ userId: 3, name: "Eliot" },
],
links: [
{ userId: 1, link: "hello->world" },
{ userId: 1, link: "world->!" },
{ userId: 3, link: "hello->back" },
{ userId: 4, link: "hello->you" },
],
};
},
methods: {
getUsernameFromUserId: function (userId) {
return this.users.filter((obj) => obj.userId == userId)?.[0]?.name ?? "Not found";
},
getUserLinksFromUserId: function (userId) {
return this.links.filter((obj) => obj.userId == userId);
},
},
};
</script>
Card.vue
<template>
<div class="card">
<h1>{{ username }}</h1>
<button #click="addLink">Add One link</button><br><br>
<span v-if="links.length == 0">No links</span>
<div class="links">
<Link v-for="link in links" :key="links.indexOf(link)" :link="link"></Link>
</div>
</div>
</template>
<script>
import Link from '../components/Link'
export default {
components:{Link},
props: {
userId: Number,
username: String,
links: { type: Array, default: () => [], required: false },
},
methods:{
addLink: function(){
this.links.push({
userId: this.userId,
link: 'newlink->cool'
});
}
}
}
</script>
Link.vue
<template>
<div>
<span>UserId: {{ this.link.userId }} Link: {{ this.link.link }</span>
</div>
</template>
<script>
export default {
props: {
link: { type: Object, default: () => [], required: false },
},
};
</script>
This is a bad way to work with props
Note: do not focus on Dev Tools too much as it can be "buggy" at times - especially if you use Vue in a wrong way. Focus on your app output
Your Card.vue component is modifying (push) a prop, which is not recommended but it sort of works if the prop is object/Array and you do not replace it, just modify it's content (as you do)
But in your case, the values passed to props are actually generated by a method! The getUserLinksFromUserId method is generating a new array every time it is called, and this array is NOT reactive. So by pushing to it, your component will not re-render and what is worse, parent's links array is not changed at all! (on top of that - if App.vue ever re-renders, it will generate new arrays, pass it to pros and your modified arrys will be forgoten)
So intead of modifying links prop in Card.vue, just emit an event and do the modification in App.vue
I have components and I would like every component template wrapped inside a div with his name.
Vue.component('search', {
template: '<div>component template</div>'
html result
<div class="component-search">
<div>component template</div>
</div>
I can't imagine why you would want to be do that but anyhow you could do the following
Vue.component('search', {
name: 'search',
template: `<div :class="wrapperClass">
<div>component template</div>
</div>`,
computed: {
componentName() {
return this.$options.name
},
wrapperClass() {
return `component-${this.componentName}`
},
}
})
I have a problem to read the passed data via props in Vue.js from parent to child. I have a list of components
components: [
{
name: "Cart Overview",
component: "CartOverview",
props: this.shopperCart
},
{
name: "Bank Account Overview",
component: "BankAccountOverview",
props: {}
},
{ name: "Verification", component: "Verification", props: {} },
{
name: "Completion Message",
component: "CompletionMessage",
props: {}
}
]
The variable "shopperCart" is set by a request from a backend.
Template of the parent component
<div class="modal-body">
<component
:is="checkoutComponents[currentStep].component"
v-bind="checkoutComponents[currentStep].props"
></component>
</div>
The user can navigate through the components with a next step button who sets the variable currentStep.
Example of one child component
<template>
<div>
<h1>Cart Oerview</h1>
{{ shopperCart }}
</div>
</template>
<script>
export default {
name: "CartOverview",
props: {
shopperCart: { type: Object, default: () => {} }
},
mounted() {
console.log("shopperCart", this.shopperCart);
}
};
</script>
The components lie on a modal. The log output only shows up displaying undefined when I refresh the page, where I can open the modal.
Can someone please help me?
Best regards,
A. Mehlen
I found myself a solution. I changed in the parent component the v-bind with :data
<div class="modal-body">
<component
:is="checkoutComponents[currentStep].component"
:data="checkoutComponents[currentStep].props"
></component>
</div>
and in the child component the name of the prop
<template>
<div>
<h1>Cart Oerview</h1>
{{ data }}
</div>
</template>
<script>
export default {
name: "CartOverview",
props: {
data: { type: Object, default: () => {} }
},
mounted() {
console.log("shopperCart", this.data);
}
};
</script>
Now it works :-)
I'm using Vue-form-wizard to create multi-step form. in one of the steps, I'm loading a dynamic component, and pass the data using v-bind and props to dynamic component. the data updates prefectly when we are on the dynamic component, but when I go to the next step all the changes lost on and rests to the parent component defaults! I tried but that doesn't do anything. here is the example of what happens:
the html:
<div id="app">
<div>
<form-wizard #on-complete="onComplete">
<tab-content v-for="tab in tabs"
v-if="!tab.hide"
:key="tab.title"
:title="tab.title"
:icon="tab.icon">
<component :is="tab.component" v-bind={'form':form}></component>
</tab-content>
</form-wizard>
</div>
</div>
and the js will be:
Vue.use(VueFormWizard)
Vue.component('step1', {
template:` <div> My first tab content <br>
</div>`,
props:['form'],
created(){
this.form = 'that other form';
alert(this.form);
}
}
)
Vue.component('step2', {
template:`<div> My second tab content </div>`
})
Vue.component('step3', {
template:`<div> My third tab content </div>`
})
Vue.component('step4', {
template:`<div> Yuhuuu! This seems pretty damn simple </div>`
})
new Vue({
el: '#app',
data() {
return {
form:'this form',
tabs: [{title: 'Personal details', icon: 'ti-user', component: 'step1'},
{title: 'Is Logged In?', icon: 'ti-settings', component: 'step2', hide: false},
{title: 'Additional Info', icon: 'ti-location-pin', component: 'step3'},
{title: 'Last step', icon: 'ti-check', component: 'step4'},
],
}
},
methods: {
onComplete: function(){
alert(this.form);
}
}
})
please help me.
thanks alot
Here:
props:['form'],
created(){
this.form = 'that other form';
alert(this.form);
}
you are changing a prop. Don't do that.
All props form a one-way-down binding between the child property and
the parent one: when the parent property updates, it will flow down to
the child, but not the other way around. This prevents child
components from accidentally mutating the parent’s state, which can
make your app’s data flow harder to understand.
Instead, the child should emit an event that the parent acts on to update the data item. I like to create a computed in the child to wrap up that behavior so I can treat it like a variable that I can assign to.
In the snippet below, I use the .sync modifier to make the update clean. I also call $nextTick after setting the value to give the event time to be handled.
Vue.use(VueFormWizard)
Vue.component('step1', {
template: ` <div> My first tab content <br>
</div>`,
props: ['form'],
computed: {
proxyForm: {
get() {
return this.form;
},
set(v) {
this.$emit('update:form', v);
}
}
},
created() {
this.proxyForm = 'that other form';
this.$nextTick(() =>
alert(this.proxyForm));
}
})
Vue.component('step2', {
template: `<div> My second tab content </div>`
})
Vue.component('step3', {
template: `<div> My third tab content </div>`
})
Vue.component('step4', {
template: `<div> Yuhuuu! This seems pretty damn simple </div>`
})
new Vue({
el: '#app',
data() {
return {
form: 'this form',
tabs: [{
title: 'Personal details',
icon: 'ti-user',
component: 'step1'
},
{
title: 'Is Logged In?',
icon: 'ti-settings',
component: 'step2',
hide: false
},
{
title: 'Additional Info',
icon: 'ti-location-pin',
component: 'step3'
},
{
title: 'Last step',
icon: 'ti-check',
component: 'step4'
},
],
}
},
methods: {
onComplete: function() {
alert(this.form);
}
}
});
<script src="https://unpkg.com/vue#latest/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-form-wizard#0.8.4/dist/vue-form-wizard.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/vue-form-wizard#0.8.4/dist/vue-form-wizard.min.css" rel="stylesheet" />
<div id="app">
<div>
<form-wizard #on-complete="onComplete">
<tab-content v-for="tab in tabs" v-if="!tab.hide" :key="tab.title" :title="tab.title" :icon="tab.icon">
<component :is="tab.component" :form.sync="form"></component>
</tab-content>
</form-wizard>
</div>
</div>
For my app I'm using two Vue components. One that renders a list of "days" and one that renders for each "day" the list of "locations". So for example "day 1" can have the locations "Berlin", "London", "New York".
Everything gets rendered ok but after removing the "Day 1" from the list of days the view isn't rendered corrected. This is what happens:
The title of the day that was removed is replaced -> Correct
The content of the day that was removed isn't replaced -> Not correct
Vue.component('day-list', {
props: ['days'],
template: '<div><div v-for="(day, index) in dayItems">{{ day.name }} Remove day<location-list :locations="day.locations"></location-list><br/></div></div>',
data: function() {
return {
dayItems: this.days
}
},
methods: {
remove(index) {
this.dayItems.splice(index, 1);
}
}
});
Vue.component('location-list', {
props: ['locations', 'services'],
template: '<div><div v-for="(location, index) in locationItems">{{ location.name }} <a href="#" #click.prevent="remove(index)"</div></div>',
data: function() {
return {
locationItems: this.locations
}
},
methods: {
remove(index) {
this.locationItems.splice(index, 1);
}
}
});
const app = window.app = new Vue({
el: '#app',
data: function() {
return {
days: [
{
name: 'Day 1',
locations: [
{name: 'Berlin'},
{name: 'London'},
{name: 'New York'}
]
},
{
name: 'Day 2',
locations: [
{name: 'Moscow'},
{name: 'Seul'},
{name: 'Paris'}
]
}
]
}
},
methods: {}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.3/vue.js"></script>
<div id="app">
<day-list :days="days"></day-list>
</div>
Please use Vue-devtools if you are not already using it. It shows the problem clearly, as seen in the image below:
As you can see above, your day-list component comprises of all the days you have in the original list, with locations listed out directly. You need one more component in between, call it day-details, which will render the info for a particular day. You may have the location-list inside the day-details.
Here is the updated code which works:
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.3/vue.js"></script>
<div id="app">
<day-list :days="days"></day-list>
</div>
Vue.component('day-list', {
props: ['days'],
template: `
<div>
<day-details :day="day" v-for="(day, index) in days">
Remove day
</day-details>
</div>`,
methods: {
remove(index) {
this.days.splice(index, 1);
}
}
});
Vue.component('day-details', {
props: ['day'],
template: `
<div>
{{ day.name }}
<slot></slot>
<location-list :locations="day.locations"></location-list>
<br/>
</div>`
});
Vue.component('location-list', {
props: ['locations', 'services'],
template: `
<div>
<div v-for="(location, index) in locations">
{{ location.name }}
[x]
</div>
</div>
`,
methods: {
remove(index) {
this.locations.splice(index, 1);
}
}
});
const app = window.app = new Vue({
el: '#app',
data: function() {
return {
days: [{
name: 'Day 1',
locations: [{
name: 'Berlin'
}, {
name: 'London'
}, {
name: 'New York'
}]
}, {
name: 'Day 2',
locations: [{
name: 'Moscow'
}, {
name: 'Seul'
}, {
name: 'Paris'
}]
}]
}
},
methods: {}
});
One other thing - your template for location-list has an error - you are not closing the <a> element. You may use backtick operator to have multi-line templates as seen in the example above, to avoid template errors.
Also you are not supposed to change objects that are passed via props. It works here because you are passing objects which are passed by reference. But a string object getting modified in child component will result in this error:
[Vue warn]: Avoid mutating a prop directly...
If you ever get this error, you may use event mechanism as explained in the answer for this question: Delete a Vue child component