'Does Vue 3 Teleport only works to port outside vue?

Vue 3 has a new Teleport feature which replaces the portal-vue plugin in vue 2. However, I found that it is impossible to port a component to a place that is controlled by vue (=in the vue app). It only worked when porting outside (body, other elements...).

const app = {
  template: `
  <div>
    <h1>App</h1>
    <div id="dest"></div>
    <comp />
  </div>`
}


Vue.createApp(app).component('comp', {
  template: `
  <div>
    A component
    <Teleport to="#dest">
      Hello From Portal
    </Teleport>
  </div>`
}).mount('#app')
<script src="https://unpkg.com/[email protected]/dist/vue.global.js"></script>

<div id="app"></div>

As you can see, the console even reports, that a teleport target needs to be already rendered. But how can I tell vue which component to render first? In my example, the target is in the dom before the teleport.

This wasn't a problem in portal-vue and is quite a bummer because it makes the whole concept way less usable.



Solution 1:[1]

The Vue error message kind of points to the problem. It says Invalid Teleport target on mount: null

The problem is that the target does not exist YET.

This can be easily fixed by only rendering the teleport portion only after the component is mounted.

It seems like this is something that Vue should handle without the explicit check. When you pass the id as a string, it's hard to tell whether the target is a Vue component or not, especially if hasn't rendered yet. But I'm only speculating on the team's intention here.

const app = {
  template: `
  <div>
    <h1>App</h1>
    <div id="dest"></div>
    <comp />
  </div>`
}

Vue.createApp(app).component('comp', {
  template: `
  <div>
    A component
    <Teleport to="#dest" v-if="isMounted">
      Hello From Portal
    </Teleport>
  </div>`,
  data: function(){
    return { 
        isMounted: false
    }
  },
  mounted(){
    this.isMounted = true
  }
}).mount('#app')
<script src="https://unpkg.com/[email protected]/dist/vue.global.js"></script>

<div id="app"></div>

Solution 2:[2]

Or one can create MountedTeleport component to extend @Daniel solution so that the model isn't poluted

<template>
    <Teleport :to="to" v-if="isMounted"><slot></slot></Teleport>
</template>

<script>
export default {
    name: "MountedTeleport",
    props: ['to'],
    data() {
        return {isMounted: false}
    },
    mounted() {
        this.isMounted = true;
    }
}
</script>

and it's used as

<MountedTeleport to="#given-selector">your html....</MountedTeleport>

if still the teleport initilizes first one may use

mounted() {
    this.$nextTick(() => {
        this.isMounted = true;
    })
}

or even if that renders first then probaly setTimeout will do the job

Solution 3:[3]

In this issue on GH contains problem solve. it must be like this

   //Root template in App.vue(it doesn't need to wrap in a div or smth else)
<template>
    <div class="modal" :style="style">
        <div class="modal-background"></div>
        <div class="modal-content">
            <div id="modal"></div>
        </div>
        <button class="modal-close is-large" aria-label="close" 
        @click="modal.hideModal"></button>
    </div>

    // App instance
    <section class="section">
            <div class="container">
                <Navbar/>
                <router-view/>
            </div>
        </section>
</template>

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Daniel
Solution 2
Solution 3 sta