33

In React I can do something like this:

// parent component
export default (){
  return (
     <div>
       <div>1</div>
       <ChildComponent />
       <div>5</div>
     </div>
  );
}

// child component
export default (){
  return (
     <React.Fragment>
       <div>2</div>
       <div>3</div>
       <div>4</div>
     </React.Fragment>
  );
};

// compiled html tags in browser .
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<div>5</div>

But in Vue , when I've done the same thing , something went difference .

// parent component
<template>
    <div>
        <div>1</div>
        <child-component />
        <div>5</div>
    </div>
</template>

<script>
    import childComponent from 'path/to/childComponent';
    export default {
      components: { childComponent }
    }
</script>
-------------------------------------------------------------
// child component
<template>
    <div id='have_to_write_in_vue'>
        <div>2</div>
        <div>3</div>
        <div>4</div>
    <div>
</template>

// compiled html tags in browser .
<div>1</div>
<div id='have_to_write_in_vue'>
    <div>2</div>
    <div>3</div>
    <div>4</div>
</div>
<div>5</div>

The difference is that 'DIV' tags are not at same level in Vue . How can I handle this ?

I'm asking this is because of something went wrong when useless nesting appearing.

RickyChan
  • 469
  • 1
  • 4
  • 9
  • 2
    There's something similar, please check [this github](https://github.com/y-nk/vue-fragment#readme). This is one of probably several modules for this. – Mike Donkers Dec 18 '19 at 09:22

8 Answers8

24

Vue 2

Package Vue-fragment facilitates root-less components for vue 2.

Vue 3

Vue 3 have official support for multi-root node components (fragments) https://v3-migration.vuejs.org/new/fragments.html

Joshua Wade
  • 4,755
  • 2
  • 24
  • 44
Jimish Fotariya
  • 1,004
  • 8
  • 21
15

The lack of a first party Fragment component in Vue drives me nuts!

I had looked at the the vue-fragment npm package, but it made me nervous at how much they were doing and felt there had to be a better way.

I came up with something extremely lightweight... It works because functional components are allowed to return multiple root nodes.

This doesn't work 100% like Reacts, however. In React, you can actually use a React.Fragment as the root node in your app, for example. This seems to only work in Vue when you're already within a component (ie: it can't be the root node of your base component).

const Fragment = {
  functional: true,
  render: (h, ctx) => ctx.children
}

to use it...

<Fragment>
  <div>Div 1</div>
  <div>Div 2</div>
</Fragment>

Super useful...

{someCondition && (
  <Fragment>
    <div>yay i can use multiple nodes</div>
    <div>:)</div>
  </Fragment>
)}

<div>
  Some inline text {condition ? (
    <Fragment>now I can use <strong>html</strong> and {variables}</Fragment>
  ) : (
    <Fragment>So many possibilities</Fragment>
  )}
</div>
Roi
  • 1,597
  • 16
  • 19
  • this looks good. could you explain how you implemented this Fragment component? – André Kelling Oct 28 '20 at 13:26
  • 2
    it is a functional component that uses ctx.children. Vue functional components allow multiple root nodes. The code for the functional component is up within the answer `const Fragment = {...}` – Roi Oct 28 '20 at 22:27
  • 1
    okay thanks. but sadly i got an error which says "[Vue warn]: Multiple root nodes returned from render function. Render function should return a single root node." on Vue v2.6.11 – André Kelling Oct 29 '20 at 07:23
  • did you add `functional: true` ? – Roi Oct 29 '20 at 20:36
  • @Roi even with `functional: true` – wobsoriano Jul 18 '21 at 14:27
  • Hello! Even me it says [Vue warn]: Multiple root nodes returned from render function. Render function should return a single root node. Using "vue": "2.6.10" – Sofienne Lassoued Jul 21 '21 at 10:00
  • Well, this is weird! I tried this method a week or 2 ago, and it didn't work. Then, I tried it today, and it worked like a charm! I wish I could remember what I tried doing back then, but either way, I can definitely say that it works. That being said, I'll try to remember this post to keep people updated on what might have been the issue if I run into that bug again. – isaacsan 123 Nov 04 '21 at 14:27
  • The issue is just where and how you use it... it can't be used as the root node for your component, only as a child in jsx – Roi Nov 04 '21 at 23:18
  • It could be that that's what I tried to do, but I don't remember. You're probably right though. – isaacsan 123 Nov 07 '21 at 11:52
  • When I do this (found the rest on the vue site) `export default Vue.component('Fragment', { functional: true, render: (h, ctx) => ctx.children})` then my child component are not accessible through `$refs` and it's an empty object. I use `Fragment` at the root level under `template` to suppress the warning `Unknown html tag Fragment` – Hassan Faghihi Jun 20 '23 at 14:50
8

Apparently, Vue.js v3 now supports fragments out of the box: Fragments (Vue.js documentation)

Joshua Wade
  • 4,755
  • 2
  • 24
  • 44
dakujem
  • 170
  • 2
  • 7
  • 2
    Vue has always supported nesting ` – tao Jan 25 '21 at 19:07
  • 1
    True, thanks for the update. Here I meant multi-root frgments, which are new in v3. In React, you would use `` (or `<>>`) to overcome the single-root limitation, which is what the OP asked for, I assume. – dakujem Jan 26 '21 at 18:54
7

Vue v3

This is a simple adaptation of @Roi's answer for Vue v3.

As @dakujem mentions, Vue v3 supports multi-root components out-of-the-box. However it does not support deeply nested fragments.

UPDATE: You can nest <template>!


<template>
  <div>
    <ul>
      <li>A</li>
      <li>B</li>
      <template>
        <li>C</li>
        <li>D</li>
      </template>
    </ul>
  </div>
</template>

Raine Revere
  • 30,985
  • 5
  • 40
  • 52
1

For those whom use Vue 3 + JSX/TSX you can do either of the following:

<>
  <p>one</p>
  <p>two</p>
</>

or

import { Fragment } from 'vue';

<Fragment>
  <p>one</p>
  <p>two</p>
</Fragment>

Note: I utilise the @vue/babel-plugin-jsx to support JSX/TSX.

ctrlplusb
  • 12,847
  • 6
  • 55
  • 57
1

TLDR: create a component Frag.vue:

<template>
    <slot></slot>
</template>

As mentioned in other answers you can nest templates, with a caveat:

You can do v-if

<template>
  <ul>
    <li>
      first
    </li>
    <template v-if="true">
      <li>
        second
      </li>
      <li>
        third
      </li>
    </template>
    <li>
      fourth
    </li>
  </ul>
</template>

producing:

<ul>
  <li>first</li>
  <li>second</li>
  <li>third</li>
  <li>fourth</li>
</ul>

or v-for

<template>
  <ul>
    <li>
      first
    </li>
    <template v-for="i in list">
      <li>
        second {{i}}
      </li>
      <li>
        third {{i}}
      </li>
    </template>
    <li>
      fourth
    </li>
  </ul>
</template>

producing:

<ul>
  <li>first</li>
  <li>second 0</li>
  <li>third 0</li>
  <li>second 1</li>
  <li>third 1</li>
  <li>fourth</li>
</ul>

However, without v-for or v-if:

<template>
  <ul>
    <li>
      first
    </li>
    <template>
      <li>
        second
      </li>
      <li>
        third
      </li>
    </template>
    <li>
      fourth
    </li>
  </ul>
</template>

it produces

<ul>
  <li>first</li>
  <li>fourth</li>
</ul>

You can work around it by always using v-if="true but it's a little less than ideal.

Having said that, because slots in templates spread all the passed children into the default slot, you can create a very simple component:

<template>
    <slot></slot>
</template>

That's it. Call it Frag.vue, import it wherever you need it. You can use it with v-if, v-for, or with neither.

<template>
  <ul>
    <li>
      first
    </li>
    <Frag v-if="true">
      <li>
        second (v-if)
      </li>
      <li>
        third (v-if)
      </li>
    </Frag>
    <Frag v-for="i in list">
      <li>
        second {{i}} (v-for)
      </li>
      <li>
        third {{i}} (v-for)
      </li>
    </Frag>
    <Frag>
      <li>
        second (nothing)
      </li>
      <li>
        third (nothing)
      </li>
    </Frag>
    <li>
      fourth
    </li>
  </ul>
</template>

producing:

<ul>
  <li>
    first
  </li>
  <li>
    second (v-if)
  </li>
  <li>
    third (v-if)
  </li>
  <li>
    second 0 (v-for)
  </li>
  <li>
    third 0 (v-for)
  </li>
  <li>
    second 1 (v-for)
  </li>
  <li>
    third 1 (v-for)
  </li>
  <li>
    second (nothing)
  </li>
  <li>
    third (nothing)
  </li>
  <li>
    fourth
  </li>
</ul>
Tigregalis
  • 607
  • 3
  • 11
0

Found this awesome directive and is battle-tested! https://github.com/privatenumber/vue-frag

<template>
    <div v-frag> <!-- This element will be unwrapped -->

        <div v-for="i in 10">
            {{ i }}
        </div>
    </div>
</template>

<script>
import frag from 'vue-frag';

export default {
    directives: {
        frag
    }
};
</script>

From the README:

How is this different from vue-fragment?

They are both designed to do the same thing. However, vue-fragment is a component and vue-frag is a directive. I made vue-frag when I saw vue-fragment didn't have any tests to ensure correct behavior, had a lot of unattended issues, and didn't seem actively maintained. In terms of size, they are both small but vue-frag is slightly smaller (993B vs 798B).

wobsoriano
  • 12,348
  • 24
  • 92
  • 162
-1

Just use a nested template tag to wrap multiple child Vue components.

<template>
  <template>
    <div class="my-first-child"></div>
    <div class="my-second-child"></div>
    <div class="my-third-child"></div>
  </template>
</template>

This will result in

<div class="my-first-child"></div>
<div class="my-second-child"></div>
<div class="my-third-child"></div>
Azzaz
  • 128
  • 3
  • 11