Skip to content

Re-rendering occurs before the execution of the #if directive. #16072

Open
@Sincenir

Description

@Sincenir

Describe the bug

I have a Modal Component that provides the style of Modal, content slot, and methods to open and close Modal.
The control method of opening and closing is implemented through the state of current,and current is set to undefiend when closing.

When calling this component, if there are child components within chilren, and first-level child component has an $effect, and second-level child component calls the clse methods, an exception whill be thrown.

Uncaught TypeError: Cannot read properties of undefined (reading 'model')

I have a #if check for the data usage, but the error still occurs. I suspect that the child components'rerendering executes earlier than the #if check.

test.svelte.ts(data model):

type State = 'init' | 'submitted';

export function createModal() {
	let state = $state<State>('init');

	return {
		get state() {
			return state;
		},

		submit() {
			state = 'submitted';
		}
	};
}

export type Model = ReturnType<typeof createModal>;

Modal.svelte:

<script lang="ts" module>
	let current = $state<{ model: Model }>();
	export function open(model: Model) {
		current = {
			model
		};
	}
	export function close() {
		current = undefined;
	}
</script>

<script lang="ts">
	import { onDestroy, type Snippet } from 'svelte';
	import type { Model } from './test.svelte';

	let {
		children
	}: {
		children: Snippet<[Model]>;
	} = $props();

	onDestroy(() => {
		console.log('destroy test');
	});
</script>

{#if current?.model}
	{@render children(current.model)}
{/if}

+page.svelte

<script>
	import { createModal } from './test.svelte';
	import TestChildren1 from './TestChildren1.svelte';
	import TestComponent, { open } from './Modal.svelte';

	const model = createModal();

	$effect(() => {
		console.log('model', model);
	});
</script>

<button
	onclick={() => {
		open(model);
	}}>open</button
>
<TestComponent>
	{#snippet children(model)}
		<TestChildren1 {model} />
	{/snippet}
</TestComponent>

TestChildren1:

<script lang="ts">
	import { onDestroy } from 'svelte';
	import TestChildren2 from './TestChildren2.svelte';
	import type { Model } from './test.svelte';

	let {
		model
	}: {
		model: Model;
	} = $props();

	$effect(() => {
		console.log('b', JSON.stringify(model));
	});

	onDestroy(() => {
		console.log('destroy component 1');
	});
</script>

<span>{model.state}</span>

<TestChildren2 {model} />

TestChildren2:

<script lang="ts">
	import { onDestroy } from 'svelte';
	import { close } from './Modal.svelte';
	import type { Model } from './test.svelte';

	let {
		model
	}: {
		model: Model;
	} = $props();

	$effect(() => {
		if (model.state === 'submitted') close();
	});

	setTimeout(() => {
		model.submit();
	}, 1000);

	onDestroy(() => {
		console.log('destroy component 2');
	});
</script>

<span>{model.state}</span>

Reproduction

example:https://github.com/Sincenir/svelte-issues-effect-and-modal

localhost... /test2

Logs

System Info

chunk-ZPWZ3TV6.js?v=a4869f9a:1549 Uncaught TypeError: Cannot read properties of undefined (reading 'model')

	in $effect
	in TestChildren1.svelte
	in Modal.svelte
	in +page.svelte
	in +layout.svelte
	in root.svelte

    at Modal.svelte:29:27
    at get model (+page.svelte:19:26)
    at $effect (TestChildren1.svelte:13:34)
(匿名)	@	Modal.svelte:29
get model	@	+page.svelte:19
$effect	@	TestChildren1.svelte:13
setTimeout		
TestChildren2	@	TestChildren2.svelte:16
TestChildren1	@	TestChildren1.svelte:18
(匿名)	@	+page.svelte:19
consequent	@	Modal.svelte:25
(匿名)	@	Modal.svelte:28

Severity

blocking an upgrade

Metadata

Metadata

Assignees

No one assigned

    Labels

    p0stuff we should fix ASAP

    Type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions