Search code examples
event-handlingsvelte

svelte: how to use event modifiers in my own components


I want to developt my own Button component and be able to handle event modifiers, like this:

<MyButton on:click|preventDefault={handler}>Click me</MyButton>

But I get the following error:

Event modifiers other than 'once' can only be used on DOM elementssvelte(invalid-event-modifier)

In MyButton I can pass the on:click event like this:

<button on:click|preventDefault>
  <slot />
</button>

But then I won't be able to use MyButton without the preventDefault

So another option would be to optionally pass event modifiers, to do something like this:

<MyButton preventDefault on:click={handler}>Click me</MyButton>

And then in MyButton.svelte to something like this (I know this doesn't work) to optionally apply the event modifier.

<script>
  export let prevenDefault=false
</script>

<button on:click|{preventDefault ? 'preventDefault' : ''}={handler}>Click me</MyButton>

Any idea about how to deal with it?


Solution

  • There are two ways that I think are applicable:

    1. Preventing default in the event callback (the easier way)
    2. Preventing default by passing in a prop (your suggestion)

    Preventing default in the event callback

    Pros Cons
    Easier & cleaner to implement Consumers of the component will have to call Event#preventDefault imperatively

    In order to set this up, you'll need two things.

    1. The <Button> component to be forwarding its event:
    <script>
      // Button.svelte
    </script>
    
    <!-- Note that we're not providing any callback, which forwards it -->
    <button on:click>
      <slot />
    </button>
    
    1. The parent component to be calling Event#preventDefault on the event:
    <script>
      // App.svelte
    
      import Button from './Button.svelte';
    </script>
    
    <Button
      on:click={(event) => {
        event.preventDefault();
    
        // your code here
      }}
    >
      Click Me!
    </Button>
    

    Preventing default by passing in a prop

    Pros Cons
    Declaratively add event modifier Consumers of the component will unwrap the original Event object from CustomEvent#detail

    You will also need two things here.

    1. The <Button> component to add its callback manually in the <script> tag
    <script>
      // Button.svelte
    
      import {
        onMount,
        onDestroy,
        createEventDispatcher,
      } from 'svelte';
    
      export let preventDefault = false;
    
      let button;
      const dispatch = createEventDispatcher();
    
      onMount(() => {
        button.addEventListener('click', onClick);
      });
    
      onDestroy(() => {
        button.removeEventListener('click', onClick);
      });
    
      function onClick(event) {
        if (preventDefault) event.preventDefault();
    
        dispatch('click', event);
      }
    </script>
    
    <button bind:this={button}>
      <slot />
    </button>
    
    1. The parent component to pass in the preventDefault prop
    <script>
      // App.svelte
    
      import Button from './Button.svelte';
    </script>
    
    <Button
      preventDefault
      on:click={({ detail: event }) => {
        // your code here
      })
    >
      Click me!
    </Button>