sveltejs / svelte

web development for the rest of us
https://svelte.dev
MIT License
80.21k stars 4.27k forks source link

Debounced Binding #9173

Open webJose opened 1 year ago

webJose commented 1 year ago

Describe the problem

While creating this example REPL about how to create a timer, I came to realize that directly binding an input field to the variable that triggers the start of the timer results in an immediate start of the timer. But what if the user is not done typing? Then the timer continuously restarts with every keystroke.

Not only does this look "bad" in a sense, but it is a performance hit.

So what's the usual option? To debounce the input event, but oh oh! I'm not handling the input event. I am using value binding. I have no place to debounce.

Describe the proposed solution

It would be ideal if Svelte could provide debouncing on binding. Something like this:

<input type="number" bind:value|debounced={variable} /> <!-- default debounce time.  400ms? -->
<input type="number" bind:value|debounced={[variable, debounceTime]} />

In the example, variable is the bound variable, while debouncedTime is a variable containing the number of milliseconds to debounce the binding event.

Alternatives considered

Drop Binding

The obvious one: Renounce the excellent Svelte feature and instead handle the input event in the "classic" way.

Bind to another Variable.

In the example REPL, the bound variable is countFrom. This is passed to the Timer component, which triggers the timer. In order to debounce this, bind the input field to a new variable, and then use reactivity ($:) to start the debounce. On timeout, the variable countFrom would be updated.

Importance

would make my life easier

MrAmericanMike commented 1 year ago

Check if what's in this repl takes you into a better direction. https://svelte.dev/repl/f55e23d0bf4b43b1a221cf8b88ef9904?version=3.12.1

As for the suggestion, it would definitely be nice if Svelte provided some debounce feature built in, for sure.

webJose commented 1 year ago

Thanks @MrAmericanMike but that's the same thing but on keyup.

robertadamsonsmith commented 1 year ago

There are plenty of ways of using stores to deal with this sort of thing cleanly, such as this modified repl, where a general purpose debounce store handles the behaviour: https://svelte.dev/repl/db628da998e6409b86a07fd12ecaeae0?version=4.2.0

It could also be implemented as two stores (a writable one, and a derived one that receives the debounced value), without much trouble.

I understand wanting this sort of behaviour to be baked into Svelte, but I personally think that bind: should always mean that both ends of the binding are in kept in sync at all times. Adding something like throttling or debouncing compromises that, and creates a lot of messy details that I don't think should be added to Svelte itself such as: should the first change be instant? Is the debouncing two-way or one-way? What happens if the timeout value changes? How do I tell if a value is currently being debounced? How do I stop the current debounce, or force immediate synchronisation?