sveltejs / svelte

Cybernetically enhanced web apps
https://svelte.dev
MIT License
78.61k stars 4.12k forks source link

Portal #3088

Closed meiseayoung closed 5 years ago

meiseayoung commented 5 years ago

is there some thing like ReactDOM.createPortal(child, container) in svelte3 ?

maxmilton commented 5 years ago

https://github.com/sveltejs/svelte/issues/1849 ?

utherpally commented 5 years ago

@MaxMilton How about event-bubbling-through-portals ?

meiseayoung commented 5 years ago

1849 ?

no, that case for Svelte2

ThomasJuster commented 5 years ago

Hi there, here's how you can do it:

<script>
// src/components/Portal.svelte
import { onMount, onDestroy } from 'svelte'
let ref
let portal

onMount(() => {
  portal = document.createElement('div')
  portal.className = 'portal'
  document.body.appendChild(portal)
  portal.appendChild(ref)
})

onDestroy(() => {
  document.body.removeChild(portal)
})

</script>

<div class="portal-clone">
  <div bind:this={ref}>
    <slot></slot>
  </div>
</div>
<style>
  .portal-clone { display: none; }
</style>
meiseayoung commented 5 years ago

Hi there, here's how you can do it:

<script>
// src/components/Portal.svelte
import { onMount, onDestroy } from 'svelte'
let ref
let portal

onMount(() => {
  portal = document.createElement('div')
  portal.className = 'portal'
  document.body.appendChild(portal)
  portal.appendChild(ref)
})

onDestroy(() => {
  document.body.removeChild(portal)
})

</script>

<div class="portal-clone">
  <div bind:this={ref}>
    <slot></slot>
  </div>
</div>
<style>
  .portal-clone { display: none; }
</style>

nice answer,thx a lot

thojanssens commented 4 years ago

@ThomasJuster

What is the purpose of the wrapping 'portal' element? Just curious.

Could be:

onMount(() => {
  document.body.appendChild(ref)
})

onDestroy(() => {
  document.body.removeChild(ref)
})

No?

thojanssens commented 4 years ago

For some reason, when the container doesn't include the previously added element, I will have the error:

TypeError: Cannot read property 'removeChild' of null

when using my version.

This error doesn't happen using @ThomasJuster 's solution

power-f-GOD commented 2 years ago

A better version of @ThomasJuster 's solution (in TypeScript though) with SvelteKit:

<!-- components/Portal.svelte -->

<script lang="ts">
  import { onMount, onDestroy } from 'svelte';

  export let target: HTMLElement | null | undefined = globalThis.document?.body;

  let ref: HTMLElement;

  onMount(() => {
    if (target) {
      target.appendChild(ref);
    }
  })

  // this block is almost needless/useless (if not totally) as, on destroy, the ref will no longer exist/be in the DOM anyways
  onDestroy(() => {
    setTimeout(() => {
      if (ref?.parentNode) {
        ref.parentNode?.removeChild(ref);
      }
    })
  })
</script>

<div bind:this={ref}>
  <slot />
</div>

And use it like so:

<!-- route.svelte -->

<script lang='ts'>
  import Portal from '../components/Portal.svelte';

  let displayPortal = false;
</script>

{#if displayPortal}
  <Portal>
    This is a portal (content). Do some inspection in dev tools to see its (new) position! :)
  </Portal>
{/if}

<button on:click={() => displayPortal = !displayPortal}>
  Toggle Portal
</button>

PS. @thojanssens This solution also fixes that: https://github.com/sveltejs/svelte/issues/3088#issuecomment-641749316

mrLuisFer commented 2 years ago

Excellent solution, works very well 😄

tsmigiel commented 1 year ago

// this block is almost needless/useless (if not totally) as, on destroy, the ref will no longer exist/be in the DOM anyways onDestroy(() => { setTimeout(() => { if (ref?.parentNode) { ref.parentNode?.removeChild(ref); } }) })

I am using SvelteKit and found an issue with this onDestroy code. I needed to make a local copy of ref for it to work properly. In my case, this onDestroy function is getting called when the Svelte component that created the Portal is destroyed (e.g., when the page is invalidated), so the onDestroy needs to work otherwise the Portal element is stuck in the DOM and not getting removed.

  onDestroy(() => {
    let _ref = ref;
    setTimeout(() => {
      if (_ref?.parentNode) {
        _ref.parentNode?.removeChild(_ref);
      }
    })
  })