Reputation: 1408
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.
Upvotes: 104
Views: 136858
Reputation: 11296
Sharing my experience with the same error as OP, but in the specific context of passing a "component-with-class" into a slot.
TLDR: Ultimately, the fix simply came from ensuring the passed component only have a single child within <template>
.
Fails:
<template>
... (multiple elements)
</template>
Works:
<template>
<div>
... (multiple elements)
</div>
</template>
I'm still trying to find where this is covered in the Vue docs (it's a templates-and-slots issue) and whether there's a better and/or proscribed way.
DETAILS
ERROR (in browser)
[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.
MyPage.vue
Note: The existence of class="icon"
triggers the error in browser console. removing it clears the warning, but styles are lost.
<AComponentWithSlots>
<template #SlotA>
<YetSomeOtherComponent name="Foo111" class="icon" />
</template>
<template #SlotB>
<YetSomeOtherComponent name="Foo222" class="icon" />
</template>
</AComponentWithSlots>
AComponentWithSlots.vue
<template>
<div>
<div><slot name="SlotA">default content slot A</slot></div>
<div><slot name="SlotB">default content slot B</slot></div>
</div>
</template>
MyComponent.vue
<template>
<YetSomeOtherComponent />
</template>
TRIALS
Removing class="icon" cleared the warning, but I no longer had the styling I wanted.
Just for fun, from sneaking suspicion of past experience, added a proper <div></div>
wrapping of the template in MyComponent.vue
and voila everything is fixed.
SOLUTION
Wrap template with a <div></div>
. I suppose this provides explicit content ready for rendering via the slot regardless of what comes out of <YetSomeOtherComponent />
.
While it's convenient to have "one-less-div" wrapping, here this is a case of better to be conservative and have the extra <div></div>
especially with the "really-dynamic" context of slots.
MyComponent.vue
<template>
<div> <!-- <<< Added this div wrapping -->
<YetSomeOtherComponent />
</div>
</template>
Upvotes: 3
Reputation: 373
In new version of vue 3.3+ you can set it like that
<script setup>
defineOptions({ inheritAttrs: false })
</script>
<template>
<h1>Title</h1>
<main v-bind="$attrs">Content</main>
<footer>Footer</footer>
</template>
Upvotes: 9
Reputation: 13389
Another way to avoid this warning is by adding the class dynamically, like this:
<my-awesome-component
ref="myComponentEl"
/>
...
myComponentEl.classList.add('awesome-class');
Upvotes: 0
Reputation: 7094
You can add defineProps to the script setup code, to get rid of this warning:
const props = defineProps(['class'])
Upvotes: 2
Reputation: 638
Michael answer is correct, but many people still fall for this error as it is triggered by some extra scenarios (edge cases) that are not covered by the documentation or not very intuitive.
The problem Just to recap, the error is triggered because we are passing an attribute (eg. disabled) and the vue component we are planning to render has 2 or more elements in its root. (this is well explained by Michael).
The edge cases:
I have covered this error in details here: https://zelig880.com/how-to-solve-extraneous-non-props-attributes-in-vue-js
Upvotes: 6
Reputation: 37793
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 (doing so also eliminates the error message).
This can be very useful. Most notably when designing specialized wrappers around standard HTML elements (like input or button) or some library component - you can tell "This is a button - you can put anything that standard <button>
accepts and it will work" without redefining all standard <button>
attributes as props of your wrapper
Upvotes: 141
Reputation: 689
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.
Upvotes: 2
Reputation: 942
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"],
...
}
Upvotes: 1
Reputation: 381
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
Upvotes: 14
Reputation: 138246
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>
Upvotes: 20