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.
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>
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With