HelloWorld
HelloWorld

Reputation: 1073

A way to render multiple root elements on VueJS with v-for directive

Right now, I'm trying to make a website that shows recent news posts which is supplied my NodeJS API.

I've tried the following:

HTML

<div id="news" class="media" v-for="item in posts">
    <div>
        <h4 class="media-heading">{{item.title}}</h4>
        <p>{{item.msg}}</p>
    </div>
</div>

JavaScript

const news = new Vue({
    el: '#news',
    data:   {
        posts:  [
            {title: 'My First News post',   msg: 'This is your fist news!'},
            {title: 'Cakes are great food', msg: 'Yummy Yummy Yummy'},
            {title: 'How to learnVueJS',    msg: 'Start Learning!'},
        ]
    }
})

Apparently, the above didn't work because Vue can't render multiple root elements.

I've looked up the VueJS's official manual and couldn't come up with a solution. After googling a while, I've understood that it was impossible to render multiple root element, however, I yet to have been able to come up with a solution.

Upvotes: 9

Views: 26590

Answers (6)

Miriam
Miriam

Reputation: 2721

In Vue 3, this is supported as you were trying:

In 3.x, components now can have multiple root nodes! However, this does require developers to explicitly define where attributes should be distributed.

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

Upvotes: 3

Alvan
Alvan

Reputation: 400

Define a custom directive:

Vue.directive('fragments', {
  inserted: function(el) {
    const children = Array.from(el.children)
    const parent = el.parentElement
    children.forEach((item) => { parent.appendChild(item) })
    parent.removeChild(el)
  }
});

then you can use it in root element of a component

<div v-fragments>
  <tr v-for="post in posts">...</tr>
</div>

The root element will not be rendered in DOM, which is especially effective when rendering table.

Upvotes: 10

Roland
Roland

Reputation: 27729

You can have multiple root elements (or components) using render functions

A simple example is having a component which renders multiple <li> elements:

<template>
 <li>Item</li>
 <li>Item2</li>
 ... etc
</template>

However the above will throw an error. To solve this error the above template can be converted to:

export default {
 functional: true,
 render(createElement) {
  return [
    createElement('li', 'Item'),
    createElement('li', 'Item2'),
  ]
 }
}

But again as you probably noticed this can get very tedious if for example you want to display 50 li items. So, eventually, to dynamically display elements you can do:

export default {
 functional: true,
 props: ['listItems'], //this is an array of `<li>` names (e.g. ['Item', 'Item2'])

 render(createElement, { props }) {
   return props.listItems.map(name => {
     return createElement('li', name)
   })
 }
}

INFO in those examples i have used the property functional: true but it is not required of course to use "render functions". Please consider learning more about functional componentshere

Upvotes: 7

Cosmin Dumitrache
Cosmin Dumitrache

Reputation: 341

The simplest way I've found of adding multiple root elements is to add a single <div> wrapper element and make it disappear with some CSS magic for the purposes of rendering.

For this we can use the "display: contents" CSS property. The effect is that it makes the container disappear, making the child elements children of the element the next level up in the DOM.

Therefore, in your Vue component template you can have something like this:

<template>
    <div style="display: contents"> <!-- my wrapper div is rendered invisible -->
        <tr>...</tr>
        <tr>...</tr>
        <tr>...</tr>
    </div>
</template>

I can now use my component without the browser messing up formatting because the wrapping <div> root element will be ignored by the browser for display purposes:

<table>
   <my-component></my-component> <!-- the wrapping div will be ignored -->
</table>

Note however, that although this should work in most browsers, you may want to check here to make sure it can handle your target browser.

Upvotes: 34

wxsm
wxsm

Reputation: 586

Multiple root elements are not supported by Vue (which caused by your v-for directive, beacause it may render more than 1 elements). And is also very simple to solve, just wrap your HTML into another Element will do.

For example:

<div id="app">
   <!-- your HTML code -->
</div>

and the js:

var app = new Vue({
  el: '#app', // it must be a single root!
  // ...
})

Upvotes: 0

RonC
RonC

Reputation: 33791

Vue requires that there be a single root node. However, try changing your html to this:

<div id="news" >
    <div class="media" v-for="item in posts">
       <h4 class="media-heading">{{item.title}}</h4>
       <p>{{item.msg}}</p>
    </div>
</div>

This change allows for a single root node id="news" and yet still allows for rendering the lists of recent posts.

Upvotes: 5

Related Questions