Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Portal #3088

Closed
meiseayoung opened this issue Jun 24, 2019 · 10 comments
Closed

Portal #3088

meiseayoung opened this issue Jun 24, 2019 · 10 comments

Comments

@meiseayoung
Copy link

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

@maxmilton
Copy link
Contributor

#1849 ?

@utherpally
Copy link

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

@meiseayoung
Copy link
Author

#1849 ?

no, that case for Svelte2

@ThomasJuster
Copy link

ThomasJuster commented Jun 26, 2019

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
Copy link
Author

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
Copy link

@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
Copy link

thojanssens commented Jun 10, 2020

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
Copy link

power-f-GOD commented Mar 12, 2022

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: #3088 (comment)

@mrLuisFer
Copy link

Excellent solution, works very well 😄

@tsmigiel
Copy link

tsmigiel commented Dec 6, 2022

// 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);
      }
    })
  })

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants