Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Vue js error: Component template should contain exactly one root element

I don't know what the error is, so far I am testing through console log to check for changes after selecting a file (for uploading).

When I run $ npm run watch, i get the following error:

"Webpack is watching the files…

95% emitting

ERROR Failed to compile with 1 errors
19:42:29

error in ./resources/assets/js/components/File.vue

(Emitted value instead of an instance of Error) Vue template syntax error:

Component template should contain exactly one root element. If you are using v-if on multiple elements, use v-else-if to chain them instead.

@ ./resources/assets/js/components/AvatarUpload.vue 5:2-181 @ ./resources/assets/js/app.js @ multi ./resources/assets/js/app.js ./resources/assets/sass/app.scss"

My File.vue is

<template>
        <div class="form-group">
            <label for="avatar" class="control-label">Avatar</label>
            <input type="file" v-on:change="fileChange" id="avatar">
            <div class="help-block">
                Help block here updated 4 🍸 ...
            </div>
        </div>

        <div class="col-md-6">
            <input type="hidden" name="avatar_id">
            <img class="avatar" title="Current avatar">
        </div>
</template>

<script>
    export default{
        methods: {
            fileChange(){
                console.log('Test of file input change')
            }
        }
    }
</script>

Any ideas on how to solve this? What is actually the error?

like image 891
Pathros Avatar asked Jul 09 '17 20:07

Pathros


People also ask

Do Vue components require a single root element?

While working on a VueJS component, you face the following error in your browser console (or in your JS linter output): Component template should contain exactly one root element. If you are using v-if on multiple elements, use v-else-if to chain them instead.

Which one is the root component of the Vue?

We can access the root Vue instance with $root , and the parent component with $parent .

Should always be multi word Vue multi word component names?

πŸ“– Rule Details. This rule require component names to be always multi-word, except for root App components, and built-in components provided by Vue, such as <transition> or <component> . This prevents conflicts with existing and future HTML elements, since all HTML elements are a single word.

What is Vue template tag?

Vue uses an HTML-based template syntax that allows you to declaratively bind the rendered DOM to the underlying component instance's data. All Vue templates are syntactically valid HTML that can be parsed by spec-compliant browsers and HTML parsers.


10 Answers

Note This answer only applies to version 2.x of Vue. Version 3 has lifted this restriction.

You have two root elements in your template.

<div class="form-group">
  ...
</div>
<div class="col-md-6">
  ...
</div>

And you need one.

<div>
    <div class="form-group">
      ...
    </div>

    <div class="col-md-6">
      ...
    </div>
</div>

Essentially in Vue you must have only one root element in your templates.

like image 81
Bert Avatar answered Oct 18 '22 03:10

Bert


For a more complete answer: http://www.compulsivecoders.com/tech/vuejs-component-template-should-contain-exactly-one-root-element/

But basically:

  • Currently, a VueJS template can contain only one root element (because of rendering issue)
  • In cases you really need to have two root elements because HTML structure does not allow you to create a wrapping parent element, you can use vue-fragment.

To install it:

npm install vue-fragment

To use it:

import Fragment from 'vue-fragment';
Vue.use(Fragment.Plugin);

// or

import { Plugin } from 'vue-fragment';
Vue.use(Plugin);

Then, in your component:

<template>
  <fragment>
    <tr class="hola">
      ...
    </tr>
    <tr class="hello">
      ...
    </tr>
  </fragment>
</template>
like image 30
Charles Bochet Avatar answered Oct 18 '22 05:10

Charles Bochet


You need to wrap all the html into one single element.

<template>
   <div>
        <div class="form-group">
            <label for="avatar" class="control-label">Avatar</label>
            <input type="file" v-on:change="fileChange" id="avatar">
            <div class="help-block">
                Help block here updated 4 🍸 ...
            </div>
        </div>

        <div class="col-md-6">
            <input type="hidden" name="avatar_id">
            <img class="avatar" title="Current avatar">
        </div>
   </div>

</template>

<script>
    export default{
        methods: {
            fileChange(){
                console.log('Test of file input change')
            }
        }
    }
</script>
like image 38
Hamoud Avatar answered Oct 18 '22 03:10

Hamoud


if, for any reasons, you don't want to add a wrapper (in my first case it was for <tr/> components), you can use a functionnal component.

Instead of having a single components/MyCompo.vue you will have few files in a components/MyCompo folder :

  • components/MyCompo/index.js
  • components/MyCompo/File.vue
  • components/MyCompo/Avatar.vue

With this structure, the way you call your component won't change.

components/MyCompo/index.js file content :

import File from './File';
import Avatar from './Avatar';   

const commonSort=(a,b)=>b-a;

export default {
  functional: true,
  name: 'MyCompo',
  props: [ 'someProp', 'plopProp' ],
  render(createElement, context) {
    return [
        createElement( File, { props: Object.assign({light: true, sort: commonSort},context.props) } ),
        createElement( Avatar, { props: Object.assign({light: false, sort: commonSort},context.props) } )
    ]; 
  }
};

And if you have some function or data used in both templates, passed them as properties and that's it !

I let you imagine building list of components and so much features with this pattern.

like image 20
blobmaster Avatar answered Oct 18 '22 04:10

blobmaster


Component template should contain exactly one root element. If you are using v-if on multiple elements, use v-else-if to chain them instead.

The right approach is

<template>
  <div> <!-- The root -->
    <p></p> 
    <p></p>
  </div>
</template>

The wrong approach

<template> <!-- No root Element -->
    <p></p> 
    <p></p>
</template>

Multi Root Components

The way around to that problem is using functional components, they are components where you have to pass no reactive data means component will not be watching for any data changes as well as not updating it self when something in parent component changes.

As this is a work around it comes with a price, functional components don't have any life cycle hooks passed to it, they are instance less as well you cannot refer to this anymore and everything is passed with context.

Here is how you can create a simple functional component.

Vue.component('my-component', {
    // you must set functional as true
  functional: true,
  // Props are optional
  props: {
    // ...
  },
  // To compensate for the lack of an instance,
  // we are now provided a 2nd context argument.
  render: function (createElement, context) {
    // ...
  }
})

Now that we have covered functional components in some detail lets cover how to create multi root components, for that I am gonna present you with a generic example.

<template>
 <ul>
     <NavBarRoutes :routes="persistentNavRoutes"/>
     <NavBarRoutes v-if="loggedIn" :routes="loggedInNavRoutes" />
     <NavBarRoutes v-else :routes="loggedOutNavRoutes" />
 </ul>
</template>

Now if we take a look at NavBarRoutes template

<template>
 <li
 v-for="route in routes"
 :key="route.name"
 >
 <router-link :to="route">
 {{ route.title }}
 </router-link>
 </li>
</template>

We cant do some thing like this we will be violating single root component restriction

Solution Make this component functional and use render

{
functional: true,
render(h, { props }) {
 return props.routes.map(route =>
  <li key={route.name}>
    <router-link to={route}>
      {route.title}
    </router-link>
  </li>
 )
}

Here you have it you have created a multi root component, Happy coding

Reference for more details visit: https://blog.carbonteq.com/vuejs-create-multi-root-components/

like image 44
Farwa Saddique Avatar answered Oct 18 '22 04:10

Farwa Saddique


In addition to Bert and blobmaster responses:

If you need to remove the root element from the DOM you can exploit css and use display: value on the root element.

like image 44
Lorenzo Fiamingo Avatar answered Oct 18 '22 03:10

Lorenzo Fiamingo


Bit of a misleading error.

What fixed it on my side was the fact that I had an additional </div> without an opening <div>.

I spotted it using Find/Replace on "div" which gave an odd number.

like image 25
LuTheZy Avatar answered Oct 18 '22 04:10

LuTheZy


Wrap everything in one div and it will resolve the issue. For example,

div

----div

----/div>

----div>

----/div>

/div

It is similar concept to React.js

like image 28
Sorya Avatar answered Oct 18 '22 04:10

Sorya


For vue 3 they removed this constraint in template syntax :

<template>
  <header>...</header>
  <main v-bind="$attrs">...</main>
  <footer>...</footer>
</template>

but it's still existing in JSX syntax :

Incorrect ❌

setup(props,{attrs}) {

return ()=>(
     <header>...</header>
     <main  {..attrs}>...</main>
     <footer>...</footer>
)
}

Correct βœ”

setup(props,{attrs}) {

return ()=>(
   <>
     <header>...</header>
     <main  {..attrs}>...</main>
     <footer>...</footer>
   </>
)
}
like image 45
Boussadjra Brahim Avatar answered Oct 18 '22 04:10

Boussadjra Brahim


I experienced this kind of issue and the issue was fixed by adding a main parent div tag or section if it is a section type of component.

<div class="list-of-friends">
     <h3>Hello World</h3>
</div>
like image 34
RMD Avatar answered Oct 18 '22 04:10

RMD