Sign in
Log inSign up
How to handle input keyup events after the user stops typing - Svelte actions and debouncing

How to handle input keyup events after the user stops typing - Svelte actions and debouncing

Frontend Team's photo
Frontend Team
·Apr 29, 2022·

5 min read

Why we should implement a custom event in Svelte?

The problem

I already know that when you need to handle some validation on text inputs, then you do it on keyup events.

And that is ok. Or not?

Actually, if the user types fast, you would like to call a function only when he/she stops typing.

So you start to think about setTimeout or setInterval strategies to implement.

Well, you should know that there is a more abstract technique, called debounce.

But if you're using svelte, you should not implement debounce everytime you need this behaviour: svelte actions let you extend any HTML element with common logic.

And in this post we will see how to add a custom event to the input, to handle the use case above.

But let's proceed step by step.

Input Validation

When working with inputs, validation is mandatory: it is necessary to perform some checks when the user stops typing, and not every time a key is pressed.

We want to have a tool to attach this behaviour easily to our inputs everytime.

Autocomplete components

The same behavior is also useful in autocomplete components, where an action must be performed to decide which elements to show: if I execute it at each keypress I could soon block the system, especially if the action involves an asynchronous call to an endpoint.

The solution is to perform the action only when the user stops typing.

In this frontend pill we will see just how to elegantly implement this functionality using Svelte actions.

How the developer will consume a custom Svelte event

As always, before implementing the "on stop typing" custom event, let's imagine the easiest way for the developer to consume it.

In Svelte, the syntax to add event listener to an input tag is as follows:

<input
      on:stopTyping={onStopTyping}
      type="text"
      bind:value={name} />

The stopTyping event does not exist, so we need to implement it. Using an action:

The result will be as follows:

<input
      use:stopTyping
      on:stopTyping={onStopTyping}
      type="text"
      bind:value={name} />

Let's then see how to implement the stopTyping action.

How to implement a custom event in Svelte to reuse on any HTML element

In Svelte there is the concept of actions, a syntax keyword to extend native HTML elements with new functionality.

To use an action, we add an attribute, use, and the function that will take care of the custom behavior (on stop typing in our case).

NOTE
Before explaining how this works, just one important note: actions only work on native HTML tags and not on Svelte components. At least for now.

How to apply the use directive in Svelte

The Svelte use directive is a kind of attribute on a HTML element that specifies a function.

The function, also called action in this context, takes the referring node as input.

export function aCustomAction(node) {
    // some logic
    return {
        destroy() { }// (optional) some logic on destroy 
    };
}

The function returns an object with one method: destroy. It is optional, and it is called when the component is destroyed.

In Svelte an action can also add some behaviour based on some extra parameters that we can pass to the function.

export function aCustomAction(node, param1) {
    // some logic
    return {
        update() { },// (optional) some logic executed when param1 gets updated 
        destroy() { }
    };
}

The update method will be executed when the param1 changes. It is optional.

The onStopTyping action

In this pill we will see how to implement the stop typing action.

Two key points: 1) it has to be used on HTML inputs 2) it will fire an event only when the user stops typing on that input

The function is simple:

export function stopTyping(node) {
    const handleKeyup = debounce((event: KeyboardEvent) => { // (1) the debounce logic
        if (node.contains(event.target)) { // (2) restrict the event to the only referring node 
            node.dispatchEvent(new CustomEvent('stopTyping')); // (3) fire the event
        }
    }, 500);

    // (4) add a generic keyup event
    document.addEventListener('keyup', handleKeyup, true);

    return {
        destroy() {
            // (5) cleanup on destroy
            document.removeEventListener('keyup', handleKeyup, true);
        }
    };
}

Let's go step by step.

First of all, we register the keyup event (4) (and cleanup it on destroy to avoid memory leaks at (5) ).

The interesting parts are:

  • fire the custom event at (3)
  • make sure the stopTyping event is fired only once when the user does not write for half a second (at (1))

To make sure that a function is only runned once after the user stops typing, we make use of the debounce function.

How does the debounce function work?

function debounce(inputFunction, timeToWaitBeforeFiringInMs = 500) {
    let timer;
    return (...args) => {
        clearTimeout(timer);
        timer = setTimeout(() => {
            inputFunction.apply(this, args);
        }, timeToWaitBeforeFiringInMs);
    };
}

Above, you can find the implementation of the dobounce function in javascript.

It is used to execute a function only once if called multiple times within a constrained time interval.

The debounce function takes a function as argument and returns a new function (that will replace the original one we passed as input).

Other use cases of the debounce function:

  • on mouse move, if we want to know when the user stops moving the mouse
  • on multiple mouse click, to react only after the last click
  • when using websockets, to execute an action only after a burst of packets arrive
  • when collecting multiple information packs, to pass to them to the backend only once every X seconds

How to consume the onStopTyping event action in Svelte

To use the function above on our inputs, we just need to import it in our Svelte component:


<script>
    import { stopTyping } from '../shared/on-stop-typing.event';

    let name = "thefrontendteam"

    async function onStopTyping() {
        // do something with name
    }
</script>

<input
      use:stopTyping
      on:stopTyping={onStopTyping}
      type="text"
      bind:value={name} />

Well done!

That's all. For more pills remember to subscribe to our newsletter :-)