kitschpatrol / svelte-tweakpane-ui

A Svelte component library wrapping UI elements from Tweakpane, plus some additional functionality for convenience and flexibility.
https:///kitschpatrol.com/svelte-tweakpane-ui
MIT License
112 stars 3 forks source link

Slider does not react immediately to store values updated in `onMount` #15

Open mechbear14 opened 3 weeks ago

mechbear14 commented 3 weeks ago

Svelte Tweakpane UI version: 1.3.0 Svelte version: 4.2.19

I'm trying to update a store value inside onMount. The value is then used and controlled by Svelte Tweakpane UI. However, the update in the store is not picked up by Svelte Tweakpane UI.

Example

<script lang="ts">
    import { writable } from 'svelte/store';
    import { Slider } from 'svelte-tweakpane-ui';
    import { onMount } from 'svelte';

    const bear = writable({ name: 'Cleeve', apples: 100 });

    onMount(() => {
        const { name } = $bear;
        bear.set({ name, apples: 60 });
    });
</script>

<p>Native input</p>
<input type="range" bind:value={$bear.apples} min={0} max={100} step={1} />
{$bear.apples}
<p>Svelte Tweakpane UI</p>
<Slider bind:value={$bear.apples} min={0} max={100} step={1} />

image

I expect the value in Tweakpane to be 60 as the native <input> element.

However, when I put these into a setInterval, it works correctly.

<script lang="ts">
    import { writable } from 'svelte/store';
    import { Slider } from 'svelte-tweakpane-ui';
    import { onMount } from 'svelte';

    const bear = writable({ name: 'Cleeve', apples: 100 });

    onMount(() => {
        setInterval(() => {
            const { name, apples } = $bear;
            bear.set({ name, apples: apples - 1 });
        }, 2000);
    });
</script>

<p>Native input</p>
<input type="range" bind:value={$bear.apples} min={0} max={100} step={1} />
{$bear.apples}
<p>Svelte Tweakpane UI</p>
<Slider bind:value={$bear.apples} min={0} max={100} step={1} />

image

It looks like a little delay is needed to make this work. Could you have a look at this and see if there's a race condition or anything?

PS: I need to use a store because there are other listeners in the actual project. Also, I need to set the value in onMount because it comes from the URL query string.

Thanks

kitschpatrol commented 2 weeks ago

Hi, thanks for opening this.

I think this is related to some variation of this issue... the <Slider> component implementation uses a reactive statement to detect changes, and in your example this has already fired on the 100 value in the same frame, and subsequent updates are ignored. Internally, I'm seeing the 60 value making it into the component, but failing to trigger the reactive statement.

This is actually kind of tricky to fix without rearchitecting Svelte Tweakpane UI to use stores instead of reactive statements in a number of places, but I'll look into it.

In the meantime, a slightly cleaner / Svelter workaround for your specific case is to wait for a tick() before updating the apple count in in onMount:

<script lang="ts">
    import { writable } from 'svelte/store';
    import { Slider } from 'svelte-tweakpane-ui';
    import { onMount, tick } from 'svelte';

    const bear = writable({ name: 'Cleeve', apples: 100 });

    onMount(() => {
        await tick();
        $bear.apples = 60;
    });
</script>

<p>Native input</p>
<input type="range" bind:value={$bear.apples} min={0} max={100} step={1} />
{$bear.apples}
<p>Svelte Tweakpane UI</p>
<Slider bind:value={$bear.apples} min={0} max={100} step={1} />

Not great, but should work for now.

Also, depending on how you're getting values from the URL query parameters, waiting for onMount() might not be necessary. You could even bind the Svelte Tweakpane UI slider directly to a store proxying the URL query parameters if that's what you're after... take a look at something like sveltekit-search-params if you're working with SvelteKit.

kitschpatrol commented 6 days ago

Hi again. This is not forgotten, but since this behavior is coming from Svelte itself I just don't see a simple fix for it in the current Svelte 4-based implementation of Svelte Tweakpane UI.

This has been identified as something that will be fixed in Svelte 5, so a future Svelte 5-based implementation of Svelte Tweakpane UI should (eventually) address this issue.

In the meantime, please use the await tick() workaround demonstrated above.

mechbear14 commented 5 days ago

Hi. Thanks for your response. Yes. I have to say await tick() is awkward, but probably it's because (I guess) it wants to collect all component states before a render, so as to avoid repeated renders. That makes sense, so I guess this is what I have to do for now.