Skip to content

[Skill Issue] State and runes are too difficult #14978

Closed as not planned
Closed as not planned
@rChaoz

Description

@rChaoz

Describe the problem

Deeply reactive state is great, but the simplicity of Svelte 4 is something that I still can't find ways to bring back. Consider this:

const [debounced, instant]= debounced({ prop: 123 })

// this triggers the debounced store to change after a delay
$instant.prop = 456

You don't really need to know the implementation of debounce() to understand how it might work, subscribing to the first store and setting the second's value after a timeout. For complicity, here's the implementation:

export function debounced<T>(initial: T, delay: number = 1000): [Writable<T>, Writable<T>] {
    let timeout: ReturnType<typeof setTimeout>
    const debounced = writable(initial)
    const instant = writable(initial)

    // update() implementations omitted
    return [
        {
            subscribe: debounced.subscribe,
            set(value) {
                clearTimeout(timeout)
                debounced.set(value)
                instant.set(value)
            },
        },
        {
            subscribe: instant.subscribe,
            set(value) {
                clearTimeout(timeout)
                instant.set(value)
                timeout = setTimeout(() => debounced.set(value), delay)
            },
        },
    ]
}

Now, I've been trying to convert this to Svelte 5, without too much success. My requirements are:

  1. clean implementation (no effects),
  2. must work with nested properties.

After all, this is why I loved Svelte so much over React - no need to worry about state or creating new objects/arrays everytime you assign. And yet, ever since I started working with Svelte 5, this is all I am allowed to think about. I remember it took me around 15 minutes to come up with the above implementation and I thought that stores are nice. How about state, tho? I managed to quickly make a function to respect my first condition:

export function debounced<T>(initial: T, delay: number = 1000): { debounced: T; instant: T } {
    let debounced = $state.raw(initial)
    let instant = $state.raw(initial)
    let timeout: ReturnType<typeof setTimeout>

    return {
        get debounced() {
            return debounced
        },
        set debounced(newValue) {
            clearTimeout(timeout)
            debounced = newValue
            instant = newValue
        },
        get instant() {
            return instant
        },
        set instant(newValue) {
            instant = newValue
            clearTimeout(timeout)
            timeout = setTimeout(() => (debounced = newValue), delay)
        },
    }
}

This looks great, but what about the second condition, that this should work with nested properties like it used to? I could make it work with some effects like:

$effect.root(() => {
    $effect(() => {
        // listen to changes anywhere in the state
        void $state.snapshot(instant)
        // propagate the change
        setTimeout(() => (debounced = newValue), delay)
    })
})

This might work, but I have so many questions:

  • Does this cause a memory leak, or can the effect root be GC'd?
  • If the answer to the above question is true, how can this be achieved instead?
  • If state is supposed to simplify stores, why did I have to learn 3 complex and weird runes and their quirks just to achieve 10% of what stores can without any knowledge whatsoever?
  • Why?

Maybe this is just a skill issue for me, but this is like the 3rd time I run into such a roadblock while trying to migrate my code. Most of the type I'm just trying to decide between an object with a current property, a function or God knows what when I could just use a store. I understand stores aren't deprecated per-se, but with $app/stores being deprecated in Kit it's tough to say what the intended direction is.

Describe the proposed solution

Thr dollar sign abstractions in Svelte 4 were the closest thing we ever got to world peach, and with every rune we stray further from God. I whole-heartedly understand why runes, but it is frustrating to 10x complicate my code when it's supposed to be the opposite, They're amazing on paper and in demos, not so much in practice.

What do I want? Not sure, but some better documentation on how to use them, when to use current versus functions, a bunch of examples for more complex cases. Maybe I just wanted to vent a little.

Importance

nice to have

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions