DEV Community

Cover image for Svelte from a React developer's perspective. Part 3: Events
José Del Valle
José Del Valle

Posted on • Originally published at delvalle.dev on

Svelte from a React developer's perspective. Part 3: Events

Hello, this is the third article where I explore Svelte from a React developer's perspective. The previous articles discussed The Basics and the topics of Reactivity and Props.

In this article I'm gonna take a look at how Svelte manages events and just like the previous articles, I'll provide some quick comparisons to React.

Today's topics introduce some new syntax and approaches that a React dev may not be used to. However, Svelte has kept all these things simple enough for us to get them real quick. The best part of all is that it's still just Javascript.

Let's now talk about:

Events

We saw some events going on in previous articles. Any Javascript dev familiarized with DOM events, event handling, and event dispatching will feel comfortable with how Svelte does all this.

As seen before, the way you attach event handlers to DOM elements is by sticking the on: prefix right before the event name.

Here's how it's done just to refresh the memory:

<input 
  on:change={handleChange}
  ...
>

And as always, you'll receive the event object via params.

One thing we can do is to declare the event handlers inline using arrow functions like so:

<div on:mousemove="{e => m = { x: e.clientX, y: e.clientY }}">
    The mouse position is {m.x} x {m.y}
</div>

Much has been talked about using arrow functions in React's JSX and how using them can affect performance due to the functions being destroyed and recreated on each re-render.

However, this is not an issue in Svelte. I'm guessing that due to its compilation process, the framework will just create and bind the function once. So we can be sure our performance won't be affected by this practice.

Modifiers

Svelte introduces something called modifiers, which help us alter the behavior of our event handlers when we declare them. The syntax will be something like on:event|modifier={eventHandler}. The modifiers can be chained as well, like so: on:event|modifier|modifier={eventHandler}.

These modifiers will run between the triggering of the event and your handler. So far you can use the following modifiers -I'm just pasting the list from Svelte's tutorial cause there's nothing more to add-:

  • preventDefault: calls event.preventDefault() before running the handler. Useful for client-side form handling, for example.
  • stopPropagation: calls event.stopPropagation(), preventing the event from reaching the next element.
  • once: remove the handler after the first time it runs.
  • self: only trigger handler if event.target is the element itself
  • passive — improves scrolling performance on touch/wheel events (Svelte will add it automatically where it's safe to do so)
  • capture — fires the handler during the capture phase instead of the bubbling phase (MDN docs)

So far we don't have these in React and even tho they are useful I don't see them strictly necessary as you can achieve the same or similar results programmatically when you need to. Still, a very cool feature that will certainly save us some extra lines of code.

Component Events

It is most likely that you'll want to dispatch events from your components. You can achieve this by creating an event dispatcher in the component you want to trigger the event from. You can then handle that event on a parent component.

In the example below, we are passing the function sayHello to a button element, it will handle the click event on that button. Then, by using the dispatcher we will send another event to the parent component:

<script>
    import { createEventDispatcher } from 'svelte';

    const dispatch = createEventDispatcher();

    function sayHello() {
        dispatch('message', {
            text: 'Hello!'
        });
    }
</script>

<button on:click={sayHello}>
    Click to say hello
</button>

The parent component will then receive the dispatched event in the handler you passed to the Inner component.

<script>
    import Inner from './Inner.svelte';

    function handleMessage(event) {
        alert(event.detail.text);
    }
</script>

<Inner on:message={handleMessage}/>

I don't think it can get simpler than this. Now, I did a small experiment following the approach I am used doing in React and it worked just the same. Here I am using a different name for the on:message attribute. I am instead passing a prop called onClick and using it inside the Inner component. Now, remember that if we want to use a prop inside a child component we have to do export let onClick at the top of our script section.

<script>
    export let onClick;

    function sayHello() {
        onClick('Hello!')
    }
</script>

<button on:click={sayHello}>
    Click to say hello
</button>

And the parent:

<script>
    import Inner from './Inner.svelte';

    function handleMessage(message) {
        alert(message);
    }
</script>

<Inner onClick={handleMessage}/>

I wonder why this approach is not in the official tutorial, maybe because of this section talking about events and not about functions as props. You could still be in the need of using a function from a parent component inside a child component, for what you would probably use this approach.

Event Forwarding

If you've worked with DOM events you may know that these events bubble, meaning that the event is first handled by the element's handler and then it goes up the tree automatically, to each of its ancestors. This doesn't happen with Component Events unless you explicitly forward them.

Let's say you have three levels of components. We'll call them Top , Middle and Bottom. If you dispatch an event from the Bottom component and want to handle it in the Top component, this means the Middle component will need to forward that event. Svelte provides us with a very simple way to do it. We just need to declare an empty on:event in our Middle component. For example:

Bottom:

<script>
    import { createEventDispatcher } from 'svelte';

    const dispatch = createEventDispatcher();

    function sayHello() {
        dispatch('message', {
            text: 'Hello!'
        });
    }
</script>

<button on:click={sayHello}>
    Click to say hello
</button>

Middle:

<script>
    import Bottom from './Bottom.svelte';
</script>

<!-- Empty on:message will forward the event to the Top component -->
<Bottom on:message/>

Top:

<script>
    import Middle from './Middle.svelte';

    function handleMessage(event) {
        alert(event.detail.text);
    }
</script>

<Middle on:message={handleMessage}/>

You can also forward events on DOM elements in case you want to handle the event in a parent component instead of the one where the DOM element lives. Let's say you have a CustomButton component but instead of handling the click event in there you forward it to the Parent. You would do something like this:

CustomButton:

<style>
    /* Some custom styles here */
</style>
<!-- Empty on:message will forward the event to the Parent component -->
<button on:click>
    Click me
</button>

Parent:

<script>
    import CustomButton from './CustomButton.svelte';

    function handleClick() {
        alert('clicked');
    }
</script>

<CustomButton on:click={handleClick}/>

Pretty simple right? I really like the way how the empty on:event will forward the event to the parent component without any extra code. React to me feels the other way around. You would probably pass a handler function as props to the children and let them execute it wherever and whenever necessary. Of course, passing a function as props several levels down can get ugly but this shouldn't happen when taking care of the architecture.

Personally, I would try to avoid forwarding events more than two levels up cause I can imagine myself tracking down the component tree trying to find which component initially dispatched the event. I guess I'll get a better view of this once I start working on an app.


This is all for the third part, there's still a lot of topics to cover but I'll work on those during the following weeks. Thanks for reading and stay tuned!

Follow me on twitter: @jdelvx

Discussion (0)