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

slots are being called twice from __layout.svelte when dealing with $page store and the logic {#if block #2527

Open
opensas opened this issue Sep 30, 2021 · 7 comments
Milestone

Comments

@opensas
Copy link

opensas commented Sep 30, 2021

Describe the bug

I have a _layout which should get the <slot /> content inside a container depending on the path.

So I use $page.path to detect when there's a change in the path, and an {#if path === 'xxx'} block to handle each case.

something like this:

<script lang="ts">
	import { page } from '$app/stores';
	$: path = $page.path;
</script>

{#if path.endsWith('MyComp')}
	<slot />
{:else}
	<div class="container">
		<slot />
	</div>
{/if}

<a href="/">root</a>
<a href="/MyComp">MyComp</a>

At start everything works fine, layout is initialized one and the component is initialized once too

From there on, every time I navigate to another page the component is initialized twice

Reproduction

Repo at gihub: https://github.com/opensas/sveltekit-slots-called-twice-error

Open the repo in gitpod: https://gitpod.io#github.com/opensas/sveltekit-slots-called-twice-error

in terminal

$ npm i -g pnpm
$ pnpm i
$ pnpm dev

click open browser (allow popups)

  • first run, everything is ok

image

init __layout.svelte
Object { path: "/" }
about to display index.svelte from __layout.svelte
init root
  • Click on MyComp, MyComp is initialized twice

image

init MyComp
Object { path: "/MyComp" }
about to display MyComp from __layout.svelte
init MyComp

Logs

No response

System Info

(as executed on gitpod console)

$ npx envinfo --system --binaries --browsers --npmPackages "{svelte,@sveltejs/*,vite}"
npx: installed 1 in 1.301s

  System:
    OS: Linux 5.4 Ubuntu 20.04.2 LTS (Focal Fossa)
    CPU: (16) x64 Intel(R) Xeon(R) CPU @ 2.80GHz
    Memory: 919.66 MB / 62.81 GB
    Container: Yes
    Shell: 5.0.17 - /bin/bash
  Binaries:
    Node: 14.17.6 - ~/.nvm/versions/node/v14.17.6/bin/node
    Yarn: 1.22.11 - ~/.nvm/versions/node/v14.17.6/bin/yarn
    npm: 6.14.15 - ~/.nvm/versions/node/v14.17.6/bin/npm
  npmPackages:
    @sveltejs/kit: next => 1.0.0-next.176 
    svelte: ^3.42.6 => 3.43.0

Severity

serious, but I can work around it

Additional Information

No response

@baseballyama
Copy link
Member

Hi.

I think this is a timing issue between component lifecycle and store lifecycle.
Before switch $page.path, already component switched from index.svelte to MyComp.svelte.
But after switched $page.path, again the component is loaded because the new <slot> tag comes.

Component lifecycle and store lifecycle don't have a relationship regarding timing.
Therefore I'm not sure how to solve this issue and I'm not sure we should solve this issue.

By the way, can you apply this change as a workaround?

{#if path.endsWith('MyComp')}
	{log('about to display MyComp from __layout.svelte')}
	<p>Comp1 from __layout.svelte</p>
{:else}
	{log('about to display index.svelte from __layout.svelte')}
	<p>index.svelte from __layout.svelte</p>
{/if}
<!-- MOVE SLOT HERE -->
<slot />

I would be happy if this information is helpful for you.

@opensas
Copy link
Author

opensas commented Sep 30, 2021

Oh, I simplified the case for the example, I need to add a container, something like

<script lang="ts">
	import { page } from '$app/stores';
	$: path = $page.path;
</script>

{#if path.endsWith('MyComp')}
	<slot />
{:else}
	<div class="container">
		<slot />
	</div>
{/if}

<a href="/">root</a>
<a href="/MyComp">MyComp</a>

thats why I can't move the slot

any other idea?

@baseballyama
Copy link
Member

IMO at least for now:

  • Option1:
    __layout.svelte can nest. doc
    So devise route folder structure for avoiding to use nested <slot>.

  • Option2:
    Create base (or wrapper) component for MyComp components.

  • Option3:
    Apply class name dynamically.
    (Accept to use useless <div>)

<script lang="ts">
	import { page } from '$app/stores';
	$: className = $page.path.endsWith('MyComp') ? 'container' : '';
</script>

<div class={className}>
	<slot />
</div>

<a href="/">root</a>
<a href="/MyComp">MyComp</a>

But anyway,
component lifecycle and store lifecycle don't have a relationship regarding timing.
Therefore write a conditional statement by $page.path at __layout.svelte has a chance to render unexpected DOM at least for a moment.

So I personally prefer option 1,2 or some related way.
(But if container class doesn't have side effects, OPTION3 is reasonable I think.)

I don't know these instant ideas can be applied to your real problem.
But again I would be happy if this information is helpful for you.

@Rich-Harris
Copy link
Member

Gah, this is tricky. We basically can't trigger page store subscribers until navigation has occurred, because otherwise they will fire inside components that are being unmounted as a result of the navigation. Which means that navigation happens, the <slot> is updated, and then path is updated causing the existing <slot> to be replaced by a new one.

If there's a solution to this, it's not obvious to me. The best workaround is probably to use named layouts.

@free-4-ever
Copy link

Since this is a SvelteKit internal issue, a pragmatic solution is to expose the current url(a piece of) value from your +layout.server.ts and use that as the key (your path) instead of relying on the $page store.
This has solved it for me.

@codenoid
Copy link
Contributor

Please fix this Mr Rich Harris, this is "small" but annoying for sveltekit user like me, thankyou mr rich harris

export const prerender = true; not help?

@krishnaTORQUE
Copy link

I am having the same issue with svelte@4.2.0 (not sveltekit)

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

6 participants