Why are my table rows rendered outside the table? DOM template parsing caveats in Vuejs

Schalk Neethling - Apr 7 - - Dev Community

The problem described here might not be something you run into often, as it mainly applies to situations where you write your HTML template directly in your HTML document. If you use a string template or a single file component, you will not encounter this problem.

When learning or wanting to prototype an idea when using Vuejs quickly, you may choose to place all of your code in a single .html document. Something like this:

<div id="app">
  <h1>WaterBear</h1>
  <table>
    <caption>
      Contributors
    </caption>
    <thead>
      <tr>
        <th>Username</th>
      </tr>
    </thead>
    <tbody></tbody>
  </table>
</div>

<script src="https://unpkg.com/vue@3"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

<script>
  Vue.createApp({
    data() {
      return {
        usernames: ["schalkneethling"],
      };
    },
  })
    .component("github-user-row", {
      template: "#github-user-row-template",
      props: ["username"],
      data() {
        return {
          user: {},
        };
      },
      async created() {
        const userAPI = "https://api.github.com/users";
        try {
          const response = await axios.get(`${userAPI}/${this.username}`);
          this.user = response.data;
        } catch (error) {
          console.log(error);
        }
      },
    })
    .mount("#app");
</script>
Enter fullscreen mode Exit fullscreen mode

Filling in the template

You will notice that the tbody is currently empty. For our rows, we might want to use an x-template, for example:

<script id="github-user-row-template" type="text/x-template">
  <tr>
      <td><img :src="user.avatar_url" height="100" width="100" />
        <a :href="user.html_url">{{ user.name }}</a>
      </td>
  </tr>
</script>
Enter fullscreen mode Exit fullscreen mode

You would then use the template inside the table:

<github-user-row
  v-for="username in usernames"
  :username="username"
></github-user-row>
Enter fullscreen mode Exit fullscreen mode

The above will loop over the usernames array and output a row for each.

The problem

If you open this page in a browser, you will find that the rows are rendered outside the table element. Why is this? You have just experienced a DOM parsing caveat when using Vue this way.

What is happening is that the browser sees the following when it parses the document.

<table>
  <caption>
    Contributors
  </caption>
  <thead>
    <tr>
      <th>Username</th>
    </tr>
  </thead>
  <tbody>
    <github-user-row
      v-for="username in usernames"
      :username="username"
    ></github-user-row>
  </tbody>
</table>
Enter fullscreen mode Exit fullscreen mode

Later in Vuejs’s lifecycle, it will replace the component with our HTML but right now, the browser does not recognize the component, marks it as invalid, and hoists it outside of the table, producing the following HTML:

<github-user-row
  v-for="username in usernames"
  :username="username"
></github-user-row>
<table>
  <caption>
    Contributors
  </caption>
  <thead>
    <tr>
      <th>Username</th>
    </tr>
  </thead>
  <tbody></tbody>
</table>
Enter fullscreen mode Exit fullscreen mode

When Vue reaches the stage in its lifecycle where it will replace the component with the HTML, you end up with the following:

<tr>
  <td>
    <img :src="user.avatar_url" height="100" width="100" />
    <a :href="user.html_url">{{ user.name }}</a>
  </td>
</tr>
<table>
  <caption>
    Contributors
  </caption>
  <thead>
    <tr>
      <th>Username</th>
    </tr>
  </thead>
  <tbody></tbody>
</table>
Enter fullscreen mode Exit fullscreen mode

The solution

The way to address this problem is to use the special is attribute inside the table element as follows:

<tbody>
  <tr
    is="vue:github-user-row"
    v-for="username in usernames"
    :username="username"
  ></tr>
</tbody>
Enter fullscreen mode Exit fullscreen mode

Note the vue: used in front of the template name. If you open the page in a browser, your table will render as expected. I hope you found this helpful and that it saved you some time in debugging.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .