Subscribe on changes!

`v-for` content rendered outside of parent element

avatar
Mar 7th 2023

Vue version

3.2.47

Link to minimal reproduction

https://jsfiddle.net/websiter/p3w7t4j8/

Steps to reproduce

Run the fiddle

What is expected?

<tr>s should render inside <table>

What is actually happening?

<tr>s render outside <table>

System Info

No idea. It happens in browser, when using https://unpkg.com/vue@3.2.47/dist/vue.global.prod.js I'm using Chrome (Version 110.0.5481.177 (Official Build) (x86_64)), on Mac (macOS Ventura Version 13.2.1 (22D68)) - I don't think it's relevant.

Any additional comments?

If we replace the v-for with:

<template v-for="driver in drivers" :key="driver.position">
  <Driver v-bind="driver"></Driver>
</template>

, it works as expected. See it here: https://jsfiddle.net/websiter/p3w7t4j8/1/

I'm not sure if this is because driver's template contains a v-for, or if it has to do with runtime compiling. I haven't checked if this has already been reported in some other shape or form.


Ran into the bug while answering a SO question. https://stackoverflow.com/a/75665085/1891677 (I believe that's kind of irrelevant, but you suggested I write this down, so...)

avatar
Mar 8th 2023

I believe this is a consequence of using an in-DOM template, as documented at https://vuejs.org/guide/essentials/component-basics.html#element-placement-restrictions.

The HTML in the template gets processed by the browser before Vue gets access to it. Consider the following example, which doesn't involve Vue:

https://jsfiddle.net/skirtle/1Lmfk4dw/

That example uses the following HTML:

<table>
  <tbody>
    <tr></tr>
    <template></template>
    <Driver></Driver>
  </tbody>
</table>

However, if you run that example and inspect the elements, you'll see that the <Driver> element has moved:

<Driver></Driver>
<table>
  <tbody>
    <tr></tr>
    <template></template>
  </tbody>
</table>

The <tr> and <template> elements are allowed inside a <tbody>, but the browser moves the <Driver> element outside the <table>.

In your example, Vue doesn't get chance to see the original markup, it only sees the innerHTML of the <div id="app"> after the browser has moved the <Driver> element to be outside the <table>.

It is largely coincidental that the Vue special tag <template> shares the same name as the native <template> element, which results in it not being moved outside the <table> during the browser's initial parse.

avatar
Mar 8th 2023

Interesting. Thanks for the explanation. Makes perfect sense.

Coincidental or not, using <template /> as the vessel for Vue templates therefore allowing them anywhere, even in elements with a very short list of allowed children, was a smart choice, to say the least.

I guess that settles this issue, then.

Thank you, @skirtles-code.

avatar
Mar 8th 2023

Pretty cool workaround/syntax, pointed out by Moritz Ringler, digged from docs:

<tr
  is="vue:driver"
  v-for="driver in drivers"
  :key="driver.position"
  v-bind="driver"
/>