Back to Blog

Updating Tree-like Component in Svelte

Updated September 6, 2023

A photo of tree

Tree-like components are notoriously difficult to create, especially if you also want to implement additional features like adding, removing, reordering, accessibility, etc. It can be the most challenging component in your entire app. In Svelte, even simply updating tree data is not trivial at all because you need to be aware of the cons in each method of updating.

In this article, I will share some tips for updating tree components in Svelte.

Rendering

This is how you do recursive rendering in Svelte.

Svelte REPL

I’m creating a helper utility called h to make tree items more manageable. As you can see, always start with the root (a single tree item that holds all the children). Doing it without a root is usually a bit more complicated when you do advanced operations later. When creating a tree, I always include a parent property, which is super helpful later if you want to do something complex.

Also, include an id property. This is very important if you want your tree to be modifiable (e.g., adding, removing, and reordering items). Without an id, expect your tree to break, with or without an error (the worst-case scenario) when you perform those operations. For the sake of simplicity, I just use Math.random(), but you can use a package like uid.

The Problems with Updating

You can click on the arrow to trigger the folding of the children, so reactivity seems to be working. But what if you want to trigger it from outside of Item.svelte?

Try setting the fold for the src item from App.svelte!

You might do something like this:

tree.children[1].fold = false

Which does the job. But what if the item is deeply nested? Of course, you don’t want to code something like this:

tree.children[1].children[1].children[5].fold = false // Yikes!

Also, keep in mind that if you store the children into a variable first, your reactivity will not work because of how compiled Svelte code works.

let item = tree.children[0]
item.fold = false // It won't trigger reactivity

Now, you can clearly see that this problem is not something trivial. Because if you want a modifiable tree, in the future, you might implement features like:

// Adding
item.appendChild(someNewItem)
// Removing
item.remove()

If you can’t store a deeply nested item inside a variable, you will not get this kind of beautiful API.

That’s sad. So, you might think, why not just store every item as writable stores? You can, but you will hit a roadblock:

ValidationError: Stores must be declared at the top level of the component (this may change in a future version of Svelte)

This is what happens if you store a writable inside a writable (nested writable) and try to access it outside the top-level function, which is most likely what you will do if you build a full app.

So, what to do?

Solution

Object mutation.

If you mutate every tree item to have a set function, it will work.

<!-- file: Item.svelte -->
<script>
  export let data

  // Mutating passed data. Because of how compiled Svelte code works,
  // calling this will trigger reactivity
  data.set = (newData) => {
    data = Object.assign(data,newData)
  } 
</script>

...

“Whoa!? I heard mutating an object directly in a case like this is a bad practice, no?”

Everything has a trade-off. We need to be wise when choosing what to use. But I can see where you’re coming from, especially if you’ve used React. I don’t think this is a bad practice at all, considering the other alternatives, which will lead to even more complicated and less straightforward code. This method also has the advantage of only triggering the part of the tree that needs changing, compared to setting the value from the tree variable, which will rebuild the entire tree.

I personally use this method in my app (a web editor), and it works very nicely.

Now you can control tree items outside of Item.svelte.

<!-- file: App.svelte -->
<script>
  setTimeout(() => {
    let item = tree.children[1]
    item.set({ fold: false })
  }, 2000)
</script>

...

Final Result

Svelte REPL

© 2023 Retually. All rights reserved.