How to pass props to component in slot? - javascript

I am developing a Vue app with pimcore and twig in the backend. I have to create a component that receives the slot (another component), and render it inside, but with dynamic props.
Here is root in viani.twig.html:
<div>
<viani-accordion>
<viani-filter-checkbox v-slot="{filterRows}"></viani-filter-checkbox>
</viani-accordion>
</div>
There is nothing special. viani-accordion is a parent component and the viani-filter-checkbox is a slot, which I have to render with appropriate props.
Here you can see the VianiAccordion.vue:
<template>
<div class="wrapper">
<AccordionTitle v-for="(item, index) in dataToRender" :item="item" :key="index">
/*I think this is incorrect, but I'm trying to prop data that I need in viani-filter-checkbox*/
<slot :filter-rows="item.items"></slot>
</AccordionTitle>
</div>
</template>
<script>
import AccordionTitle from './Accordion-Title';
export default {
name: "Viani-Accordion",
components: {AccordionTitle},
data() {
return {
dataToRender: [
{
name: 'first item',
items: [
{
name: 'oil olive',
quantity: 10,
},
{
name: 'subitem 2',
quantity: 11,
},
]
},
{
name: 'second item',
items: [
{
name: 'subitem 1',
quantity: 10,
},
{
name: 'subitem 2',
quantity: 11,
},
]
}
]
}
},
}
</script>
Then I have another deeper child component Accordion-Title.vue that is responsible for rendering the slot (so I have to pass the slot through the multiple components):
<template>
<div v-if="isOpen" class="child-wrapper">
/*I think this is incorrect, but I'm trying to prop data that I need in viani-filter-checkbox*/
<slot :filterRows="item.items"></slot>
</div>
</template>
<script>
export default {
name: "Accordion-Title",
props: {
item: {
type: Object,
default: null
}
}
}
</script>
and finally Viani-FiltersCheckbox.vue:
<template>
<div>
//child component which we don't need in this case
<FilterCheckboxRow v-for="(item, index) in filterRows" :item="item" :key="index"/>
</div>
</template>
<script>
import FilterCheckboxRow from './FilterCheckboxRow'
export default {
name: "VianiFilterCheckbox",
components: {
FilterCheckboxRow
},
props: {
//here I expect to get array to render, but got default value (empty array)
filterRows: {
type: Array,
default: function () {
return []
}
},
},
}
</script>
So I need to pass the props (filterRows) to the component (Viani-FiltersCheckbox.vue), which is rendered as a slot. I have read this and this, but still don't get where the mistake and how to get the props I need.

It looks like you're trying to access your props through props.XXX. That's typically only done in templates for functional components. Otherwise, the props would be accessed without the props. prefix (i.e., props.item.items should be item.items).
And to pass filterRows from the scope data to the child component, declare a <template>, and then move your child into that, binding filterRows there:
<viani-accordion>
<!-- BEFORE: -->
<!-- <viani-filter-checkbox v-slot="{filterRows}"></viani-filter-checkbox> -->
<template v-slot="{filterRows}">
<viani-filter-checkbox :filterRows="filterRows"></viani-filter-checkbox>
</template>
</viani-accordion>

Related

Vue.js - prop not reactive when initialized via PHP

I have a parent component (item-list) to which I pass items from a PHP view (Laravel/Blade). The item-list consists of list-items (child component). Inside a list-item I want to modify/delete this specific item. After triggering the deletion inside the child component, an event will be emitted back to parent component where the item will be finally removed from the items list.
It seems like this approach is not reactive. If I instead use data() with some dummy data for the list items of a prop filled via PHP, it's reactive.
How can I guarantee reactivity for props passed via PHP?
Parent component:
<template>
<my-list-item
v-for="item of items"
:key="item.id"
:item="item"
#remove="removed"
></my-list-item>
<br />
</template>
<script>
import MyListItem from './my-list-item'
export default {
// parent component
name: "my-list",
components: {
MyListItem
},
data() {
return {
// this will work. items are reactive
// items: [
//
// { id: 1, message: 'foo' },
// { id: 2, message: 'bar' },
//
// ]
}
},
props: {
// not reactive, items come from PHP view (Laravel/Blade).
items: {
type: Array,
default: undefined
}
},
methods: {
removed: function (item) {
_.remove(this.items, item);
}
}
};
</script>
Child component:
<template>
<!-- item stuff here (label, etc.)
...
-->
{{ item.message }}
<!-- remove link -->
remove
</template>
<script>
// child
export default {
name: "my-list-item",
props: {
item: {
type: Object,
}
},
emits: [
'remove'
],
methods: {
remove: function (item) {
this.$emit("remove", item);
}
},
};
</script>
PHP view (list.blade.php):
#extends('dashboard')
#section('title', 'List items')
#section('content')
<my-list :items="{{ Session::get('items') }}"></my-list>
#endsection

Vue 3 passing array warning: Extraneous non-props attributes were passed to component but could not be automatically inherited

please, I'm learning a VueJS 3 and I have probably begineer problem. I have warn in browser developer console like this one:
The Message is:
[Vue warn]: Extraneous non-props attributes (class) were passed to component but could not be automatically inherited because component renders fragment or text root nodes.
I'm passing array of objects to the child Component. In my parent views/Home.vue compoment I have this implemenation:
<template>
<div class="wrapper">
<section v-for="(item, index) in items" :key="index" class="box">
<ItemProperties class="infobox-item-properties" :info="item.properties" />
</section>
</div>
</template>
<script>
import { ref } from 'vue'
import { data } from '#/data.js'
import ItemProperties from '#/components/ItemProperties.vue'
export default {
components: {
ItemDescription,
},
setup() {
const items = ref(data)
return {
items,
}
},
</script>
In child compoment components/ItemProperties.vue I have this code:
<template>
<div class="infobox-item-property" v-for="(object, index) in info" :key="index">
<span class="infobox-item-title">{{ object.name }}:</span>
<span v-if="object.type === 'rating'">
<span v-for="(v, k) in object.value" :key="k">{{ object.icon }}</span>
</span>
<span v-else>
<span>{{ object.value }}</span>
</span>
</div>
</template>
<script>
export default {
props: {
info: {
type: Array,
required: false,
default: () => [
{
name: '',
value: '',
type: 'string',
icon: '',
},
],
},
},
}
</script>
It doesn't matter if I have default() function or not. Also doesn't matter if I have v-if condition or not. If I have cycle in the Array, I got this warning
Data are in data.js file. The part of file is here:
export const data = [
{
title: 'White shirt',
properties: [
{ name: 'Material', value: 'Cotton', type: 'string', icon: '' },
{ name: 'Size', value: 'M', type: 'string', icon: '' },
{ name: 'Count', value: 4, type: 'number', icon: '' },
{ name: 'Absorption', value: 4, type: 'rating', icon: '💧' },
{ name: 'Rating', value: 2, type: 'rating', icon: '⭐️' },
{ name: 'Confort', value: 2, type: 'rating', icon: '🛏' },
{ name: 'Sleeves', value: 'Short', type: 'string', icon: '' },
{ name: 'Color', value: 'White', type: 'string', icon: '' },
],
},
]
PS: Application works but I'm afraid about that warning. What can I do please like right way?
I will be glad for any advice. Thank you very much.
Well I think the error message is pretty clear.
Your ItemProperties.vue component is rendering fragments - because it is rendering multiple <div> elements using v-for. Which means there is no single root element.
At the same time, you are passing a class to the component with <ItemProperties class="infobox-item-properties" - class can be placed on HTML elements only. If you place it on Vue component, Vue tries to place it on the root element of the content the component is rendering. But because the content your component is rendering has no root element, Vue does not know where to put it...
To remove the warning either remove the class="infobox-item-properties" or wrap the content of ItemProperties to a single <div>.
The mechanism described above is called Fallthrough Attributes ("Non-prop attributes" Vue 2 docs). It is good to know that this automatic inheritance can be switched off which allows you to apply those attributes by yourself on the element (or component) you choose besides the root element. This can be very useful. Most notably when designing specialized wrappers around standard HTML elements (like input or button) or some library component...
The ItemProperties component has multiple root nodes because it renders a list in the root with v-for.
Based on the class name (infobox-item-properties), I think you want the class to be applied to a container element, so a simple solution is to just add that element (e.g., a div) in your component at the root:
// ItemProperties.vue
<template>
<div>
<section v-for="(item, index) in items" :key="index" class="box">
...
</section>
</div>
</template>
demo
You could also prevent passing down attributes in child components by doing this:
export default defineComponent({
name: "ChildComponentName",
inheritAttrs: false // This..
})
Source: https://vuejs.org/guide/components/attrs.html
This could also be triggered from parent components that have props: true in their route definition. Make sure that you add props: true only in the components that you actually need it and have some route params as props.
You are passing a class attribute to ItemProperties without declaring it.
Declare class in props options api should solve this issue.
ItemProperties.vue
...
export default {
props:["class"],
...
}

Mutate props in Vue.js 3 - or how do I reflect a prop's changes to it's parents

I am migrating things from Vue.js 2 to Vue.js 3. During my migration, I just mentioned that eslint does warn me, because I am mutating props.
This is an example of an element that causes this:
<template>
<ToggleField
v-model="item.checked"
:name="`item.${name}.checked`"/>
</template>
<script>
import ToggleField from "./ToggleField";
export default {
name: 'TestField',
components: {ToggleField},
props: {
name: {
type: String,
required: true,
},
item: {
type: Object,
},
},
}
</script>
This element is deeply nested and every parent element passes the :item-attribute to the next "level" until it's finally displayed and changeable due v-model.
This is an example:
Parent view
<template>
<CustomForm name="test" :item="item" />
<!-- Reflect changes on item here -->
{{ item }}
</template>
<script>
import CustomForm from "./CustomForm";
export default {
components: {
CustomForm
},
data: () => ({
item:
{name: 'Foo', 'checked': false},
}),
}
</script>
CustomForm
<template>
<!-- Do other fancy stuff here -->
<TestField :name="name" :item="item"/>
</template>
<script>
import TestField from "./TestField";
export default {
name: 'CustomForm',
components: {TestField},
props: {
name: {
type: String,
required: true,
},
item: {
type: Object,
},
},
}
</script>
TestField
<template>
<ToggleField
v-model="item.checked"
:name="`item.${name}.checked`"/>
</template>
<script>
import ToggleField from "./ToggleField";
export default {
name: 'TestField',
components: {ToggleField},
props: {
name: {
type: String,
required: true,
},
item: {
type: Object,
},
},
}
</script>
So my question is: How can I update the item and reflect the changes to it's parent (and it's parent, and it's parent again, if necessary) without running into the prop-mutation?

Component communication in Vue.js unable to render properly

In the Parent component I have:
<todo-item v-for="(todo, index) in todos" :key="todo.id" :todo="todo" :index="index">
</todo-item>
which just iterates through todos array and gets each todo object and by using props passes each Object and its index to the child component. todo-item registered in Child component.
todos is an array of objects:
todos: [
{
'id': 1,
'title': 'Object 1'
},
{
'id': 2,
'title': 'Object 2'
}
]
Child component:
<template>
<div class="todo-item">
<div class="todo-item-left">
<div>{{ todo.title }}</div>
</div>
</div>
</template>>
<script>
export default {
name: 'todo-item',
props: {
todo: {
type: Object,
required: true
},
index: {
type: Number,
required: true
}
}
}
</script>
I don't know why it doesn't render each todo on the page, I have a blank page. Even though in Vue DevTools It shows that I have these objects.
Did I miss something?
EDIT:
There is an error, sorry the error flag were off hence didn't saw it.
Error message:
[Vue warn]: Unknown custom element: - did you register the component correctly? For recursive components, make sure to provide the "name" option.
As you can see above I did register the component in Child component.
And yes I did import the child component in Parent component by doing:
//Parent component
import ToDoItem from './ToDoItem'
export default {
name: 'todo-list',
components: {
ToDoItem,
},
You have problem with cases so you should import that component in parent one as follows :
import TodoItem from './TodoItem'
and register it like :
export default{
....
components:{
TodoItem
}
....
}
for more details check this
a common mistake
dont forget import child component in parent component
import ChildComponent fromt './ChildComponent';
export default{
components:{
ChildComponent
}
}

Props data not showing on child component Vue.JS

I am passing an array of object as props from parent to the child component. This works fine.
When I do {{ questionList }} its show me my whole data, so the props is being received.
When I try to output the data in the child component as {{ questionList[0].question }}, this also works fine. But it only shows me the first question.
But when I try to output my data as
{{ questionList.question }}, nothing is being shown.
So how can I ouput all my questions?
Please check out my code below.
Parent Component - questionForm
<template>
<div class="form" >
<questions v-for="question in questionList" vbind:key="question.question" v-bind:questionList="questionList" />
</div>
</template>
<script>
import questionList from '../questions/questionList.js'
import questions from '../components/questions'
export default {
name: 'questionForm',
components: {
questions
},
data () {
console.log(questionList)
return {
questionList
}
}
}
Child component - questions
<template>
<div class="questions">
<p> {{ questionList.question }} </p>
</div>
</template>
<script>
export default {
name: 'questions',
props: ['questionList'],
data () {
return {
}
}
}
Imported data - questionList.js (the data is much bigger but I reduce it for readability purposes)
export default [
{
question: 'Sex',
id: 0,
options: [
{label: 'Woman', value: 2},
{label: 'Male', value: 1}
]
},
{
question: 'Age',
id: 1,
options: [
{label: '8-12', value: 1},
{label: '12-14', value: 2},
{label: '14-16', value: 2}
]
}]
You are passing the whole questionList while you should pass the single question object in the child component like this
<template>
<div class="form" >
<questions v-for="question in questionList" v-bind:key="question.question" v- bind:object="question" />
</div>
</template>
<script>
import questionList from '../questions/questionList.js'
import questions from '../components/questions'
export default {
name: 'questionForm',
components: {
questions
},
data () {
console.log(questionList)
return {
questionList
}
}
}
where question here v-bind:object="question" is the object from the loop
and then you can use it in the child component in the props
<template>
<div class="questions">
<p> {{ object.question }} </p>
</div>
</template>
<script>
export default {
name: 'questions',
props: ['object'],
data () {
return {}
}
}
You are binding the whole list to each question child with:
v-bind:questionList="questionList"
It seems like you want each child to have a single question since you are using v-for in the parent. If that’s the case, just bind a single question to each child:
<questions v-for="question in questionList" vbind:key="question.id" v-bind:question="question" />
Then in the child you should be able to access the properties of that question (make sure to change the prop name in the child component to question):
{{question.question}}
I changed the code a little bit,
but what you are trying to do is basically looping through the question list then passing each question as a prop in the child question component.
so try this :D
Inside The Parent Component
<template>
<div class="form" >
<questions v-for="{question, index} in questionList" :key="index" :question="question" />
</div>
</template>
<script>
import questionList from '../questions/questionList.js'
import questions from '../components/questions'
export default {
name: 'questionForm',
components: {
questions
},
data () {
console.log(questionList)
return {
questionList
}
}
}
Next inside your child component
<template>
<div class="questions">
<p> {{ question.question }} </p>
</div>
</template>
<script>
export default {
name: 'questions',
props: ['question'],
}
}

Categories