1

I have a code like below. 5 first componets are rendered correctly, but next 3 are not. Only first one from those 3 is rendered:

enter image description here

const test = {
  data() {
    return {
      count: 0
    };
  },
  props: {
    text: {
      type: Number,
      required: true
    }
  },
  template: `
    <button @click="count++">
      [ {{text}} ] You clicked me {{ count }} times.
    </button>
    `
};

const app = Vue.createApp({});
app.component("test", test);
app.mount("#app");
<!DOCTYPE html>
<html>
  <body>
    <div id="app">
      <div v-for="i in 5">
        <test :text="i" />
      </div>

      <hr />

      <test :text="99" />
      <test :text="42" />
      <test :text="55" />
    </div>

    <script
      id="vue"
      src="https://unpkg.com/vue@3.0.11/dist/vue.global.prod.js"
    ></script>
  </body>
</html>

Obviously... I want to be able to use components not only in the loops. What I missed?

BTW, this same behavior I was able to reproduce with latest vue 2.x (with slightly different vue app initialization code, ofc)

noisy
  • 6,495
  • 10
  • 50
  • 92

1 Answers1

3

The problem is caused by self-closing elements. See updated demo below...

Why

In HTML5 spec, self closing tags are allowed only on "void" elements (Void elements are those that may not ever contain any content - as br, img etc.) - see this SO question - Are (non-void) self-closing tags valid in HTML5?

So what happens here is that your not valid HTML is interpreted by the browser (when the page is loaded - even before Vue is started) like this (just run your snippet, comment out app.mount() and inspect the HTML in the Dev Tools):

    <div id="app">
      <div v-for="i in 5">
        <test :text="i"></test>
      </div>

      <hr>

      <test :text="99">
        <test :text="42">
          <test :text="55">
          </test>
        </test>
      </test>
    </div>

Now it is easy to see why v-for works ok, 1st component is rendered ok but the rest is not (test component has no default slot)

Note this is problem only when using in-DOM templates and works fine with string templates (template option) or SFC files (and self-closing tags are actually recommended by Vue ESLint rules and Vue style guide)

const test = {
  data() {
    return {
      count: 0
    };
  },
  props: {
    text: {
      type: Number,
      required: true
    }
  },
  template: `
    <button @click="count++">
      [ {{text}} ] You clicked me {{ count }} times.
    </button>
    `
};

const app = Vue.createApp({});
app.component("test", test);
app.mount("#app");
<!DOCTYPE html>
<html>
  <body>
    <div id="app">
      <div v-for="i in 5">
        <test :text="i"></test>
      </div>

      <hr />

      <test :text="99" ></test>
      <test :text="42" ></test>
      <test :text="55" ></test>
    </div>

    <script
      id="vue"
      src="https://unpkg.com/vue@3.0.11/dist/vue.global.prod.js"
    ></script>
  </body>
</html>
tony19
  • 125,647
  • 18
  • 229
  • 307
Michal Levý
  • 33,064
  • 4
  • 68
  • 86
  • Wow. Thanks. But next question is: Why!?!? And why this works in loops? – noisy May 20 '21 at 11:14
  • a little bit of explanation here: https://vuejs.org/v2/style-guide/#Self-closing-components-strongly-recommended – noisy May 20 '21 at 11:20