Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 426
Fuzzyma Avatar asked Aug 29 '20 22:08

Fuzzyma


2 Answers

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>
like image 88
Daniel Avatar answered Oct 18 '22 03:10

Daniel


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

like image 27
Bat Man Avatar answered Oct 18 '22 04:10

Bat Man