Read props from parent component in a list of dynamic components - vue.js

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 :-)

Related

VueJs Pass array of object to child component do not refresh on changes

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

VueJS child component button changes class of parent

I have a Navigation component with a button to toggle dark mode/light mode. When I click this button, I would like to change a class in the App.vue. How to pass the data from the button to the App.vue?
I believe I have to emit something from the Top Nav and v-bind the class in App.vue, but I don't quite understand how to get it change the class.
When I run this, the button does not change the class on the div.
App.vue
<template>
<div id="app" :class="[isActive ? 'darkmode' : '']>
<header>
<top-nav #change-mode="enableDarkMode"></top-nav>
</header>
...
</div>
</template>
<script>
export default {
name: "App",
components: {
TopNav,
},
props: ['isActive'],
data() {},
methods: {
enableDarkMode(isActive) {
console.log(isActive)
},
},
};
</script>
Top Nav Component
<template>
...
<div>
<button
:class="[isActive ? 'dark' : 'light']"
#click="toggle">
{{ isActive ? "DarkMode" : "LightMode" }}
</button>
</div>
</template>
<script>
export default {
name: "TopNav",
data() {
return {
isActive: false,
};
},
components: {},
methods: {
toggle() {
this.isActive = !this.isActive;
this.$emit('change-mode', this.isActive )
console.log('emit child')
},
},
};
</script>
From the snippet you provided it seems like the App.vue has isActive props instead of data for the method enableDarkMode to manage. Vue's props is not to be updated by the component they belong to because of how the data flow works in Vue props. With the App.vue being the parent of Top Nav component, you probably want it to be like this:
App.vue
<template>
<div id="app" :class="[isActive ? 'darkmode' : '']>
<header>
<top-nav #change-mode="enableDarkMode"></top-nav>
</header>
...
</div>
</template>
<script>
export default {
name: "App",
components: {
TopNav,
},
// this is probably not needed because App.vue is the parent component
// props: ['isActive'],
data() {
return {
isActive: false,
};
},
methods: {
enableDarkMode(isActive) {
// manages the data
this.isActive = isActive;
},
},
};
</script>
Top Nav component
<template>
...
<div>
<button
:class="[isActive ? 'dark' : 'light']"
#click="toggle">
{{ isActive ? "DarkMode" : "LightMode" }}
</button>
</div>
</template>
<script>
export default {
name: "TopNav",
data() {
return {
isActive: false,
};
},
components: {},
methods: {
toggle() {
this.isActive = !this.isActive;
this.$emit('change-mode', this.isActive )
console.log('emit child')
},
},
};
</script>

Vue.js how can i add dynamic component to the dom

in my app i need to add components to the dom after fetching data from an api
i have a component called Carousel and a component called Home
so i don't know how many carousels i need in my Home component
the question is : how can i add components using for loop from inside methods:{}
My Code :
Home.vue
<template>
<div>
<template
v-for="widget in widgets"
v-bind:is="Carousel">
{{ widget }}
</template>
</div>
</template>
<script>
import Carousel from './widgets/Carousel.vue'
export default {
components: {
Carousel
},
data() {
return {
page_content: [],
widgets: [],
}
},
created() {
this.getHomeContent();
},
methods:
{
addWidget() {
this.widgets.push('Carousel')
},
getHomeContent() {
window.axios.get(window.main_urls["home-content"]).then(response => {
this.page_content = JSON.parse(JSON.stringify(response.data));
this.addWidget()
});
}
}
}
</script>
Carousel.vue
<template>
<div>
<div :class="this.type == 'full' ? '' : 'container'">
Slider
</div>
</div>
</template>
<script>
export default
{
name:'Carousel',
props:[
'type',
'showTitle',
'dots',
'controls',
'data'
],
methods:
{
}
}
</script>
How to create as much carousels is i need using loop
the above code just give me a string 'carousel'
i want the component to be rendered
Here is a working example of dynamic components with properties
<div>
<template v-for="item in widgets">
<component :is="item.name" :name="item.name" v-if="item.name==='carousel'"></component>
</template>
</div>
The data in widgets
widgets: [
{name: 'carousel'},
{name: 'carousel'},
{name: 'test'},
{name: 'carousel'},
]
then it will create three components.
I created a test component like this to check this
components: {
'carousel': {
template: "<div>Dynamic component {{name}}</div>",
props: ["name"]
}
},

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

Sending property through router not working

I am not sure if I am doing this correctly, this is my router view definition:
{
path: '/page',
name: 'page',
component: Page,
props: true,
meta: { requiresAuth: true}
},
Then I access the view like this:
self.$router.push({ name: 'page', params: {stuff: true }})
This is my page component:
<template>
<div class="page">
{{ stuff }}
</div>
</template>
<script>
export default {
name: 'Page',
data() {
return {
stuff: false
}
},
}
</script>
It looks like stuff property is not being set to true, its always false
Our code will pass stuff as component props, not component's data
<template>
<div class="page">
{{ stuff }}
</div>
</template>
<script>
export default {
name: 'Page',
props: ['stuff']
}
</script>
In document:
When props is set to true, the route.params will be set as the
component props