svelte: how to use event modifiers in my own components
Asked Answered
T

3

8

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?

Transcendent answered 19/10, 2021 at 11:59 Comment(2)
As <MyButton> is not a DOM element, you won't be able to add modifiers except "once". Maybe a component for a button is not accurate ? I'm not sure there is any gain about it.Unlimber
Why not add preventDefault as a bool prop. In your MyButton componend you can use: event.preventDefault() if the preventDefault prop is true.Enlil
H
8

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>
Hadwyn answered 22/10, 2021 at 3:48 Comment(1)
This does the job, but looks like a poor workaround. I wish Svelte had a better way of doing this.Locksmith
Q
3

Custom button component

<script lang="ts">
    export let preventDefault: boolean = false;
    export let stopPropagation: boolean = false;
    type MouseEvent = Parameters<MouseEventHandler<HTMLButtonElement>>[0];
    type $$Events = {
        click: MouseEvent;
    };
</script>

{#if preventDefault}
    {#if stopPropagation}
        <button {...$$restProps} on:click|preventDefault|stopPropagation
            ><slot /></button>
    {:else}
        <button {...$$restProps} on:click|preventDefault><slot /></button>
    {/if}
{:else if stopPropagation}
    <button {...$$restProps} on:click|stopPropagation><slot /></button>
{:else}
    <button {...$$restProps} on:click><slot /></button>
{/if}

Custom button component usage

<Button
  type="button"
  class="primary-button"
  title="this is a button"
  on:click={onClick}
  preventDefault
  stopPropagation
>
  Send complaint
</Button>

If you can endure to insane branch statements drive your brain crazy, this code should achieve what you're looking for. I know. this is really poor workaround. I really want Svelte has a better way of doing this.

Quindecennial answered 19/7, 2023 at 3:31 Comment(0)
D
0

Here is an alternative that works, at least for me:

<button on:click|preventDefault>
  <MyButton on:click={handleClick}>
    Click Me
  </MyButton>
</button>
Dogear answered 2/6, 2024 at 5:38 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.