Skip to content

Nested ifs cause template effect to run when it shouldn't #16148

Closed
@rChaoz

Description

@rChaoz

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:

  1. 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 executing
  2. inner = undefined - nothing happens. Both the inner if block-effect and template-effect depend on it, but they are paused, so they don't run.
  3. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions