Edit on GitHub

Logic blocks

Logic blocks let your view branch and loop. JSlop has two: {#if} and {#each}.

{#if}

{#if count > 0}
  <p>positive</p>
{/if}

Add an {:else} for the other branch:

{#if loggedIn}
  <p>welcome back</p>
{:else}
  <a href="/login">log in</a>
{/if}

The condition is any expression. It’s re-evaluated whenever its dependencies change.

Note

{:else if condition} chains aren’t a parse case yet — nest {#if} blocks inside {:else} for now.

Scopes and cleanup

When a branch becomes inactive, JSlop disposes everything inside it: child component instances, per-item effects, event listeners, the DOM itself. There’s no zombie state lurking under a hidden subtree.

Re-entering a branch builds it fresh from scratch.

{#each}

{#each todos as item}
  <li>{item}</li>
{/each}

Add an index binding after a comma:

{#each todos as item, i}
  <li>{i}: {item}</li>
{/each}

The item (and optional index) is scoped to the block body — it’s just a regular local binding, not a reactive cell.

Wrap a key expression in (...) after the bindings:

{#each todos as t, i (t.id)}
  <li>{i}: {t.text}</li>
{/each}

With a key, JSlop reconciles the list by identity, not by position:

  • Matching keys keep their existing DOM, per-item effects, and any nested component state across reorders and inserts.
  • New keys get a fresh build.
  • Removed keys have their per-item scope disposed and their DOM removed.

Always key any list that mutates by anything other than append-only. Without a key, the list falls back to dispose-then-rebuild on every change — correct, but you lose focus, scroll position, input draft state, and animation state on every update.

// ✓ keyed — focus and scroll survive
{#each items as it (it.id)}
  <li><input bind:value={it.label} /></li>
{/each}

// ✗ unkeyed — every change rebuilds every <li>
{#each items as it}
  <li><input value={it.label} /></li>
{/each}

Nesting

You can nest {#if} and {#each} freely:

<ul>
  {#each groups as g (g.id)}
    <li>
      <h3>{g.name}</h3>
      {#if g.items.length === 0}
        <p>empty.</p>
      {:else}
        <ul>
          {#each g.items as it (it.id)}
            <li>{it.label}</li>
          {/each}
        </ul>
      {/if}
    </li>
  {/each}
</ul>

Components inside {#each}

Components nested in an each are instantiated lazily per iteration — each row owns its own instance. With a key, that instance survives reorders.

{#each rows as row (row.id)}
  <RowView data={row} />
{/each}

Warning

SSR-restored state for components nested inside {#each} does not currently round-trip through the state capsule. They re-create from scratch on hydration. Top-level component state and props do round-trip — this gap is each-scoped. Track it on the roadmap.

Empty lists

There’s no {:empty} clause yet. Pair an {#each} with an {#if}:

{#if items.length === 0}
  <p>nothing here yet.</p>
{:else}
  <ul>
    {#each items as it (it.id)}
      <li>{it.label}</li>
    {/each}
  </ul>
{/if}

What’s not here

Note

Logic blocks JSlop does not yet implement (see roadmap):

  • {:else if} chains
  • {#await promise} / {:then} / {:catch}
  • {#key expression} for forcing a re-mount
  • {#snippet} / {@render} for reusable view fragments

See also

  • Template syntax — what {expr} does outside logic blocks.
  • Reactivity — how scopes are disposed when branches and rows go away.
Last updated: May 15, 2026