Description
Describe the bug
Given this component:
<script>
let outer = $state(true)
let inner = $state(123)
</script>
{#if outer}
<div out:fade>
{#if inner}
{inner.toString()}
{/if}
</div>
{/if}
Setting outer
to false
, and before the transition finishes, inner
to undefined
and outer
back to true
, causes the inner if contents to run, even though the condition is false
, and crashes trying to evaluate undefined.toString().
I went a bit in-depth into the code, debugging this, and found this sequence of events to be the culprit:
outer = false
- outer if block-effect re-runs, and pauses its consequent-effect. The consequent-effect is not destroyed due to the out transition, and it (and any children) stop executinginner = undefined
- nothing happens. Both the inner if block-effect and template-effect depend on it, but they are paused, so they don't run.outer = true
- outer if block-effect re-runs and resumes its consequent-effect. Since resuming is recursive, the inner if block-effect is scheduled, and so is the inner template effect.
If we stopped here, all would be good - inner if-block would run, pausing (and destroying) its consequent-effect and children, so the template effect would never run. However in step (3) above I left out that, when a template effect is scheduled (not run), it evaluates its body. This happens because its body is wrapped in a derived (I'm guessing for perf reasons), which is immediately executed, unlike an effect. In this case, it tries to execute undefined.toString()
and crashes.
Reproduction
https://svelte.dev/playground/4366a43f154a41a98af58677fe6b370d?version=pr-16140
Severity
serious