Vue-fragment – many components inside template

Vue is very nice JS framework with – in my opinion – very clean and simple structure. It’s separate HTML, CSS and JavaScript, in effect it’s much cleaner than React. But Vue is not ideal, and sometimes its limitations can be strange and annoying. Good example is that we must use only one child elements on every component. It doesn’t matter in many cases, but try to render table with some child components and conditions – I promise you, it will be real nightmare.

Rows in components and Vue limitation

What exactly am I talking about? Let’s say you have table and inside it many rows. Some rows are parents elements, some are childs. There can be many childs after every parent. Here is an simple JSON structure for this example, with nested hierarchy:

elements: [
	{
		name: 'Test'
		foo: 'bar'
	},
		{
		name: 'Test2'
		foo: 'bar2'
		elements: [
			{
				name: 'ChildTest1'
				foo: 'bar2'
			},
			{
				name: 'ChildTest1'
				foo: 'bar2'
			}
		]
	}
]

Everything is clean and simple. It’s array so we can do two things – render each row in loop, or create custom component and also use its in in rendering loop. We need second approach because of some different fields and different logic on row types, parents and children. Still simple? Yes, but there is nightmare. How do we create parent with children rows in our custom component? It’s not a problem to use it the same level (component can reuse himself), but we must first render parent, and then children in loop. There is issue – two rows, but Vue limited us to use only one parent element in component. This example will not work and will throw an error:

<template>
	<tr class="parent">
		(...)
	</div>
	<tr v-if="element.elements.length" v-for=(child, index) in element.elements>
		(...)
	</div>
</template>

In most cases we just put any container like div inside template to store many children, but here it isn’t good approach – table will be broken, it requires rows, not divs. According to official github, it isn’t a bug, but rendering limitation related to for example performance. We should agree with that, it’s ok. Of course we can try to change our structure completely, but it may be terrific in some situations.

Solution: vue-fragment

Better option is to use vue-fragment – special, custom Vue component, that allows us to create “virtual” elements to store other. It’s created to bypass this strange Vue limitation and works great. First step is to install vue-fragment:

npm i -s vue-fragment

And then we can use it on our Vue files as component (there is also option to use it as plugin in Vue globally, check official documentation):

<template>
	<fragment>
		<tr class="parent">
			(...)
		</div>
		<tr v-if="element.elements.length" v-for=(child, index) in element.elements>
			(...)
		</div>
	</fragment>
</template>


<script>
import { Fragment } from 'vue-fragment'

export const ParentWithChilds {
  components: { Fragment },
  (...)
}
</script>

And it’s all. Fragment will create virtual element and remove it on displaying – we can see only comment information about fragment usage, but DOM will correct and also, Vue will not throw any errors related to many components inside template. It’s small, but very, very useful package.