I'm trying to access properties I'm passing on to my slot. But my slotProps are undefined.
As I'm still new to Vue and I've read their docs I still can't seem to figure out why I can't access the props data.
Problem
I'm trying to access the slotProps in my child components created, but it's undefined
emphasized text
<template>
<div>
<slot :data="data" :loading="loading"></slot>
</div>
</template>
Child
<template v-slot:default="slotProps">
<div >
</div>
</template>
<script>
export default {
name: "child"
created: function() {
console.log("slotProps", slotProps);
}
};
</script>
You can use this object to follow with your child property
demo: https://stackblitz.com/edit/vue-kkhwzc?file=src%2Fcomponents%2FHelloWorld.vue
Updated code Child
<template v-slot:default="slotProps">
<div >
</div>
</template>
<script>
export default {
name: "child"
created: function() {
console.log("slotProps", this.slotProps);
}
};
</script>
You do not need the created() life cycle hook to achieve what you want. There are few things to clear up:
What you are using is actually called scoped slots. They are useful because, unlike when using the default and named slots, the parent component can not access the data of its child component(s).
What you call a Child is actually the parent component.
Child.vue component should be something like this:
<template>
<div>
<main>
<slot :data="data1" :loading="loading1" />
</main>
</div>
</template>
<script>
export default {
name: 'Page',
data () {
return {
data1: 'foo',
loading1: 'bar'
}
}
}
</script>
In a Parent.vue component, you can access the data of the above component as follows:
<template>
<child>
<template v-slot="slotProps">
{{ slotProps.data }},
{{ slotProps.loading }}
</template>
</child>
</template>
<script>
import Child from '#/components/Child.vue'
export default {
components: { Child }
}
</script>
Or you can also destruct the objects on the fly as follows:
<template>
<child>
<template v-slot="{data, loading }">
{{ data }},
{{ loading }}
</template>
</child>
</template>
<script>
import Child from '#/components/Child.vue'
export default {
components: { Child }
}
</script>
This is the clean way to access data of a child component from the parent using scoped slots.
Related
What I am trying to achieve:
Display an image from dynamic link in Post.vue, which follows the layout of PostLayout.vue
In PostLayout.vue, I have a <slot> named postFeaturedImage, and inside the slot, there is a <div>, I want to use the image as the background of it.
What I am using:
Laravel, InertiaJS, Vue 3
My codes are:
Post.vue:
<template>
<PostLayout>
<template #postfeaturedImage>
<!-- Here I want to display the image -->
</template>
</PostLayout>
</template>
<script>
import PostLayout from '#/Layouts/PostLayout'
export default {
data() {
return {
featured_image: ''
}
},
components: {
PostLayout,
},
props: {
post: Object /* Passed the prop from Controller */
},
mounted () {
this.featured_image = this.post.featured_image
}
}
</script>
PostLayout.vue:
<template>
<slot name="postfeaturedImage" :bgImage="bgImage">
<div :style="`background-image:url(${bgImage}); height: 75vh;`"></div>
</slot>
</template>
<script>
export default {
}
</script>
I've removed all irrelevant codes. I am a beginner in Vue 3 and Inertia and in need of help!
An alternative approach will be creating a FeaturedImage component. Also, you can reference the post image directly from the props you receiving. There's no need for the data method and the mounted in this case.
<template>
<PostLayout>
<template #postfeaturedImage>
<FeaturedImage :src="post.featured_image" />
</template>
</PostLayout>
</template>
<script>
import PostLayout from '#/Layouts/PostLayout'
import FeaturedImage from '#/Layouts/FeaturedImage'
export default {
components: {
PostLayout,
FeaturedImage
},
props: {
post: Object
}
}
</script>
Add a props to your PostLayout.vue
<script>
export default {
props: {
bgImage: { type: String },
},
};
</script>
And give a value to that props in your Post.vue
<template>
<PostLayout :bgImage="featured_image"> </PostLayout>
</template>
And if you ever want to have a post without an image and a different layout you should do :
<template>
<PostLayout>
<template #postfeaturedImage> post without background image</template>
</PostLayout>
</template>
I went through similar questions but I couldn't find the answer and none of them worked for me.
Vuejs v-for Passing data Parent to Child
Vue - Passing parent data with same key name in v-for to child component
vue js how to pass data from parent, v-for loop list to child component with method
Pass data object from parent to child component
Vue.js - Pass in Multiple Props to Child in V-For
VUE / VUEX: How To Pass Data From Parent Template To Child Template
Parent.vue
<div class="col-sm-2" v-for="(item,index) in itemsData" :key="index">
<ItemWidget :item="item" />
</div>
<script>
import { mapState, mapActions } from "vuex";
import ItemWidget from "#/components/item/ItemWidget";
export default {
components: { ItemWidget },
computed: {
...mapState("item", ["itemsData"])
},
created() {
this.getItemList();
},
methods: {
...mapActions("item", ["getItemListX"]),
getItemList() {
this.getItemListX();
}
}
};
</script>
ItemWidget.vue
<template>
<div class="label">
<div class="label-value">{{ item.code }}</div>
</div>
</template>
<script>
export default {
props: ["item"]
};
</script>
itemsData is taken from the vuex by using MapState to the parent and itemsData is populated using created() method in the parent via an axios call in the vuex.
Error 1.
[Vue warn]: Error in render: "TypeError: _vm.item is undefined"
Error 2.
TypeError: "_vm.item is undefined"
How can I fix this?
Update
itemsData: [
{
code: "Test",
}
]
You should populate itemsData in computed method using ...mapState
Parent.vue
export default {
data: function () {
return {
items: this.itemsData
}
},
computed:{
...mapState('module/namespace', ['itemsData'])
}
}
<div class="col-sm-2" v-for="(item,index) in items" :key="index">
<ItemWidget :item="item" />
</div>
There is another way to declare your props:
<template>
<div class="label">
<div class="label-value">{{ item.code }}</div>
</div>
</template>
<script>
export default {
props: {
type: Object,
default: null
}
};
</script>
In vue, I have a component A with a slot that returns an object as the slot-scoped in the component B using it:
component A template:
<template>
<div>
<slot :myObject="myObject" />
</div>
</template>
component B template:
<template>
<component-a>
<template slot-scope="{myObject}">
<!-- uses myObject -->
</template>
</component-a>
</template>
<script>
module.exports={
data(){
return {
myObject: null // This never updates with the new value
}
}
}
</script>
Everything works fine in the html template of component B, however, I cannot access to myObject in the script of component B. I could create a child component (C) that accepts myObject as a prop and have all the needed logic there, but I would like to avoid that.
If you use slot-scope you are basically passing data to the slot, from the component, which is hosting the slot (not the with the content of the slot).
In your case if you want to use data slot-scope, you have to pass the data from component A. So the data-source myObject must exist in component A.
So the right approach would look something like this:
Component A
<template>
<div>
<slot :myObject="myObject" />
<button #click="changeMyObject">Change MyObject</button>
</div>
</template>
<script>
export default {
name: "slot-scope-component",
data(){
return {
myObject: {
value: "ABC"
}
}
},
methods:{
changeMyObject() {
this.myObject = {
value: "XYZ"
};
}
}
}
</script>
Component B
<template>
<ComponentA>
<template slot-scope="props">
{{props.myObject}}
</template>
</ComponentA>
</template>
<script>
import ComponentA from '#/components/ComponentA';
export default {
components: {
ComponentA
},
}
</script>
As well there was a little spelling mistake: You wrote slot-scoped instead of slot-scope
You can improve that code further by using destructuring:
slot-scope="{myObject}"
Using Vue.js,
How to create componentA that gets componentB as a prop, and print it inside of it?
example:
index.vue
<template>
<div>
<componentA :componentPlaceHolder="componentB"></componentA>
</div>
</template>
<script>
import componentA from './compnentA.vue';
import componentB from './componentB.vue'
export default {
name: 'index',
components: {componentA,componentB }
}
</script>
componentA.vue
<template>
<div>
{{componentPlaceHolder}}
</div>
</template>
<script>
export default {
name: 'componentA',
props: {
'componentPlaceHolder': {}
}
}
</script>
There are some issues to your implementation:
You have gotten the scope wrong: componentPlaceHolder lives in the parent scope, not in that of component A. Read: Compilation Scope.
Use :is (i.e. v-bind: is) for dynamic component binding. The data bound should reference the key of the component.
Since you are nested additional components in another component in the same context, that means you have to interweave the content. This is done by using slots, declared in <component-a>.
Avoid using case-sensitive DOM elements, use kebab case instead, i.e. <component-a> instead of <componentA>, since HTML elements are case-insensitive (<componentA> and <componenta> will be treated the same).
Here is the updated code:
<template>
<div>
<component-a>
<customComponent :is="componentPlaceHolder"></customComponent>
</component-a>
</div>
</template>
<script>
import componentA from './componentA.vue';
import componentB from './componentB.vue'
export default {
name: 'index',
components: {
'component-a': componentA,
'component-b': componentB
},
data: {
componentPlaceHolder: 'component-b'
}
}
</script>
And then in your componentA.vue:
<template>
<div>
<!-- Slot will interweave whatever that is found in <componentA> -->
<slot></slot>
</div>
</template>
<script>
export default {
name: 'componentA'
}
</script>
Proof-of-concept example
If in doubt, here is a live proof-of-concept example:
var componentA = {
template: '#component-a'
};
var componentB = {
template: '#component-b'
};
new Vue({
el: '#app',
components: {
'component-a': componentA,
'component-b': componentB
},
data: {
componentPlaceHolder: 'component-b'
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.3/vue.min.js"></script>
<div id="app">
<component-a>
<!-- DOM elements in here will be interweaved into <slot> -->
<customComponent :is="componentPlaceHolder"></customComponent>
</component-a>
</div>
<template id="component-a">
<div>
<p>I am component A</p>
<slot></slot>
</div>
</template>
<template id="component-b">
<p>I am component B</p>
</template>
Footnote:
The VueJS readme is exceptionally composed, and I suggest here are some things that you can read up on that is very relevant to your use case:
Compilation Scope
Dynamic Components
Content Distribution with Slots
Using single file architecture I'm trying to pass data (an object) from a parent component to a child:
App.vue
<template>
<div id="app">
<app-header app-content={{app_content}}></app-header>
</div>
</template>
<script>
import appHeader from './components/appHeader'
import {content} from './content/content.js'
export default {
components: {
appHeader
},
data: () => {
return {
app_content: content
}
}
}
</script>
appHeader.vue
<template>
<header id="header">
<h1>{{ app_content }}</h1>
</header>
</template>
<script>
export default {
data: () => {
return {
// nothing
}
},
props: ['app_content'],
created: () => {
console.log(app_content) // undefined
}
}
</script>
Seems to be such a trivial task and probably the solution is quite simple. Thanks for any advice :)
You're almost there.
In order to send the app_content variable from App.vue to the child component you have to pass it as an attribute in the template like so:
<app-header :app-content="app_content"></app-header>
Now, in order to get the :app-component property inside appHeader.vue you will have to rename your prop from app_component to appComponent (this is Vue's convention of passing properties).
Finally, to print it inside child's template just change to: {{ appContent }}