I am trying to do the most basic thing in Svelte but I keep failing. I want the button element to trigger the visibillity of the ul-element with classname “this-one”. I am stuck in the “vanilla js”-approach and can not wrap my head around how to achieve this the svelte-way. Any pointers?
<ul>
{#each item.children as child (child.id)}
<li>
<div>
<a href={child.route.path}
on:click={() => dispatch('click')}>
<span>{child.name}</span>
</a>
{#if child.children?.length > 0}
<button>
<i class="chevron-down"></i>
</button>
{/if}
</div>
{#if child.children?.length > 0}
<ul class="this-one">
{#each child.children as grandChild}
<li>
<a href={grandChild.route.path}
on:click={() => dispatch('click')}>
{grandChild.name}
</a>
</li>
{/each}
</ul>
{/if}
</li>
{/each}
</ul>
You would have to store the visibility information either on the item object itself or an external object, e.g. a set, map or in an array by index. (You might not want to have it on the item for various data organization reasons.)
E.g. using a Set
1:
let expanded = new Set();
function toggle(id) {
if (expanded.has(id)) {
expanded.delete(id);
} else {
expanded.add(id);
}
// trigger reactivity, since object is mutated internally using methods
expanded = expanded;
}
<button on:click={() => toggle(child.id)}>
<i class="chevron-down"></i>
</button>
...
{#if child.children?.length > 0 && expanded.has(child.id)}
<ul class="this-one">
...
REPL
If the state is directly on the item, it is the most straightforward:
<button on:click={() => child.expanded = !child.expanded}>
<i class="chevron-down"></i>
</button>
...
{#if child.children?.length > 0 && child.expanded}
<ul class="this-one">
...
REPL
1 In Svelte 5 you can use the SvelteSet
from 'svelte/reactivity'
, which updates without any dummy assignment. (Such assignments generally do not do anything in Svelte 5.)