Svelte 5 $state rune Class vs Object reactivity

by webmaster 2024-11-07 updated: 2024-11-10 #svelte

Svelte 5 was finally released after a long development cycle, and it brings the concept of runes, which changes the state paradigm quite a bit compared to Svelte 3/4. I stayed away from Svelte during this period, because I wanted to work with the finished product.

Playing around with the $state rune, I found that I generally like it. It was a lot harder to use it in a global, sharable store (because the official documentation is lacking in this respect), but a friendly Svelte contributor on bsky helped me with a working example.

I'm building a tiny app that uses a shared store to hold arrays of objects. This is both to test Svelte 5's new features, and to scratch an idea I had.

Now, the objects in my arrays are actual JavaScript class instances. I thought it makes sense to use classes instead of simple objects, because there are some OOP features here that can come in handy (in particular having logic in the constructor).

I got stuck on an issue though. When I updated a property of an object (class instance) in the array (incrementing a counter), it wasn't responsive. It took a while to figure out (my JS isn't my strong suit).

Long story short, it turns out that if you store a class instance using $state, its properties are not reactive. While you can modify the internal state of the object, it won't re-render in the browser.

Note You can, in fact, make class properties reactive. Read all the way down for the best solution.

So what can you do? I found 2 solutions (you might know more):

  • store a simple object instead of a class - {} instead of new Widget()
  • or add a toObject method to the class which returns the properties you need, as a simple object (the implementation here might be naive, but it does what I need so I don't care)

Since I was adamant to use classes, I decided on the 2nd solution.

Here's the code, to illustrate what's happening. You can also find it in the REPL.

<script>
    let collection = $state([])

    class Ob {
        id = null
        name = ''
        count = 1

        constructor(id, name) {
            this.id = id
            this.name = name
        }

        toObj() {
            return {
                id: this.id,
                name: this.name,
                count: this.count,
            }
        }
    }

    function incr(i) {
        collection[i].count++
    }

    collection.push(new Ob(1, '❌ Class'))
    collection.push({ id: 2, name: "✅ Object", count: 1 })
    collection.push(new Ob(3, '✅ Class.toObj()').toObj())
</script>


{#each collection as col, i}
    <button type="button" onclick={() => incr(i)}>+</button>
    {JSON.stringify(col)} 
    <br>
{/each}

And here's a GIF of the 3 scenarios.

Svelte 5 state rune reactivity Class vs Object comparison

I hope this is helpful in case you run into the same problem. It sure enough got me unstuck and helped me understand Svelte 5's reactivity just a tiny bit more.


But wait, there's more. It turns out you can actually make the 1st scenario (Class properties) reactive by wrapping the class properties in $state. Who woulda thunk it? Here's an updated REPL. There's no need for the toJSON function with this approach.

<script>
    let collection = $state([])

    class Ob {
        id = $state(null)
        name = $state('')
        count = $state(1)

    collection.push(new Ob(1, '✅ Class'))

Thanks to Pablopang.svelte over on Bluesky for helping with this!


Mea Culpa I must be a dumbass. After all this, I discovered that Class reactivity is clearly laid out in the docs. I could swear it wasn't there the first 5 times I read that entire page. But it was - my brain probably skipped it for some reason. So all I did here was to find a couple of non-optimal solutions and waste someone's time on social media, when I could have just RTFM more carefully 🤦‍♂️

Liked this article? Share it on your favorite platform.
Replies
Dylan </closingtags.com>
Dylan </closingtags.com> @[email protected]

@brbcoding Oooooh that's a good one. Thanks for sharing!

Reply
Rockfish1292
Rockfish1292 @[email protected]

@brbcoding if I recall Rich talked about this somewhere along the way and I believe his take was to make each property a $state object itself, possibly $derived? I'm on my phone so I haven't tested any code though

Reply
???????? Placebo Domingo
???????? Placebo Domingo @[email protected]

@jkibble yeah I think that might be an option too. The dev who helped me initially hinted to it. Unfortunately I've had to figure this stuff out on my own because the docs aren't very helpful in this respect. I'll experiment and update the post if I find another way.

Reply