tgordon18
tgordon18

Reputation: 1869

Using Throttle With Svelte Store

I'm trying to throttle the execution of a function in Svelte. However, using throttle within an auto-subscription seems to break it. Consider the following example (REPL):

<script>
  import throttle from 'lodash.throttle';
  import { writable } from 'svelte/store';
    
  const arr = writable(['A', 'B', 'C']);
    
  function foo(val) {
    console.log('foo: ', val);
  }
</script>

{#each $arr as a}
  <button on:click={throttle(() => { foo(a); }, 1000)}>
    Button {a}
  </button>
{/each}

The execution of foo(a) is not throttled at all. If you remove the {#each $arr as a} block and just pass a string to foo, you'll see that it works as intended.

I'm assuming this has to do with the event loop and how Svelte auto-subscriptions work but don't know the exact reason. Wondering if anyone knows a) why this is happening and b) what a possible solution could look like?

Upvotes: 0

Views: 648

Answers (1)

Geoff Rich
Geoff Rich

Reputation: 5482

If you look at the code Svelte generates for this, you can see that it is regenerating the throttled function on every click when you pass a store value. This resets the throttle timer on every click.

dispose = listen(button, "click", function () {
    if (is_function(throttle(click_handler, 1000))) 
        throttle(click_handler, 1000).apply(this, arguments);
});

For whatever reason, this does not happen when you pass a regular string.

dispose = listen(button, "click", throttle(click_handler, 1000));

This may be a bug in Svelte, but I'm not sure. It might be worth opening an issue on the GitHub repo.

I was able to work around it by generating the throttled functions ahead of time:

<script>
    import throttle from 'lodash.throttle';
    import { writable } from 'svelte/store';
    
    const arr = writable(['A', 'B', 'C']);
    
    function foo(val) {
        console.log('foo: ', val);
    }
    
    $: throttledFns = $arr.map(val => getThrottled(val));
    
    function getThrottled(val) {
        console.log('getting throttled');
        return throttle(() => { foo(val); }, 1000);
    }
</script>

{#each $arr as a, idx}
    <button on:click={throttledFns[idx]}>
        Button {a}
    </button>
{/each}

This will regenerate the throttled functions when the store array changes, but not on every click.

You can also generate a throttled version of foo once and use that, but that will throttle all clicks to the buttons (e.g. if you click A and then click B, the click to B will be throttled).

<script>
    // as before
    
    const throttledFoo = throttle(foo, 1000);
</script>

{#each $arr as a, idx}
    <button on:click={() => throttledFoo(a)}, 1000)}>
        Button {a}
    </button>
{/each}

Upvotes: 1

Related Questions