svelte event parameter type for typescript
Asked Answered
W

5

30

So new to svelte but it is so small it is perfect for a job i am working on.

Went for the typescript option: https://svelte.dev/blog/svelte-and-typescript

How or where can i find the types for custom component events:

A simple login component form:

<script lang="ts">
  import { createEventDispatcher } from 'svelte'

  const dispatch = createEventDispatcher()
  let isSubmitting = false
  const handleSubmit = (e: HTMLFormElement) => {
    e.preventDefault()
    isSubmitting = true
    const payload = {
      username: e.target.username.value,
      password: e.target.password.value,
    }
    dispatch('submit', payload)
  }
</script>

<form on:submit={handleSubmit}>
    <label for="username"><b>Username</b></label>
    <input type="text" placeholder="Enter Username" name="username" required id="username">

    <label for="password"><b>Password</b></label>
    <input type="password" placeholder="Enter Password" name="password" required id="password">

    <button type="submit" disabled={isSubmitting}>Login</button>
</form>

Included in another component to handle the submit:

<script lang="ts">
  import Login from './molecules/Login.svelte'
  const loginHandle = function (a: any) {
    console.log(a)
  }
</script>

<main class="{open}">
   {#if !authenticated}
      <Login on:submit={loginHandle}/>
   {/if}
</main>

Right now there is an ugly any added to the loginHandle but when dumping the event to console it looks to be very svelte specific.. where can i find the type?

Weiner answered 27/9, 2020 at 11:8 Comment(3)
I guess you can type it using Event.Olander
yup - detail and explicitOriginalTarget are added. Detail contains the payload as a developer are interested in, would have expected to have these in a SvelteEvent type somewhere i guess.Weiner
Svelte don't have custom type for the events because they are vanilla js one. They are not like react which fake some events and have custom type. So you should be fine with the Event type. Should I write it as the answer ?Olander
U
40

For full typing, change the event raiser to:

const dispatch = createEventDispatcher<{submit:{username:string, password:string}}>()

And the event consumer to:

const loginHandle = function (a: CustomEvent<{username:string, password:string}>) {
    console.log(a.detail.username) //username is type string
    console.log(a.detail.password) //password is type string
}

This will make dispatch("submit", "wrongDetailType") fail. And it eliminates the a.detail being of type "any" in the handler.

Unbeliever answered 12/6, 2021 at 16:55 Comment(1)
This is wonderful! However I notice it does not raise an error when calling dispatch with no details, the signature is still details?: [type]. Do you know a way to change that? If I want to give type to my details I'd like to be sure they are present. edit does not seem possible: github.com/sveltejs/svelte/blob/222a9dd/src/runtime/internal/…Lucre
A
9

How or where can i find the types for custom component events:

You can pass an object to the generic of the createEventDispatcher function. Where key is a custom event name and value is a detail object.

Child component (<Calendar />)

<script lang="ts">
  import { createEventDispatcher } from 'svelte'

  const dispatch = createEventDispatcher<{ select: Date }>()

  function onSelect(date: Date): void {
    dispatch('select', date)
  }
</script>

...

Parent component

<script>
  function handleSelect (event: CustomEvent<Date>) {
    console.log('Selected date', event.detail)
  }
</script>

<Calendar on:select={handleSelect} />

Usage example

enter image description here

Ahoy answered 17/5, 2022 at 14:2 Comment(1)
The problem with this approach is that it doesn't tie the handler and emitter together. There is nothing stopping you from createEventDispatcher<{timestamp: Date}> and handle(event: CustomEvent<string>). This makes refactoring cumbersome.Poultice
P
4

Svelte now ships with a CustomEvent interface definition. So you can declare your function as below.

const loginHandle = function (a: CustomEvent) {
    console.log(a)
}
Poindexter answered 16/2, 2021 at 13:45 Comment(2)
You can even go further and type the detail member of CustomEvent by using CustomEvent<SomeType>. See my answer below.Unbeliever
This doesn't really answer the question. Even if you use CustomEvent, the detail property will still be any.Nanine
P
3

I found that this is not possible using the Svelte event system. You can declare the type on each side but there is nothing at this time to validate that the types (or event the event names) in the component match that in the code using the component.

However this can be worked around using callback properties rather than events. The only downsides that I am aware of is that the onSubmit name is less distinct than on:submit as a callback/event and that you need to declare events that you propagate. Overall this seems like a very small price to pay for type checking on both the event names and event values. You can also make required callbacks which can be useful for components which don't make sense to use without listening to specific events.

Example

This is a minimized example of the code in the original question.

Component

<script context="module" lang="ts">
    export interface Payload {
        username: string,
        password: string,
    }
</script>
<script lang="ts">
    export let onSubmit: (payload: Payload) => void;

    function handleSubmit(e: SubmitEvent) {
        e.preventDefault()
        onSubmit({
            username: e.target.username.value,
            password: e.target.password.value,
        });
    }
</script>

<form on:submit={handleSubmit}>
    <input name=username required>
    <input type=password name=password required>
    <button type=disabled>Login</button>
</form>

Note: To make the handler optional simply set a noop function as a default value:

export let onSubmit: (payload: Payload) => void = () => {};

Caller

<script lang="ts">
    import Login from "./Login.svelte"
    import type {SubmitEvent} from "./Login.svelte"
    function handleLogin (event: SubmitEvent) {
        console.log(event)
    }
</script>

<main>
    <Login onSubmit={handleLogin}/>
</main>
Poultice answered 11/3, 2023 at 20:6 Comment(0)
L
0

Create the type for all the events that your component will dispatch, and create the type details for the CustomEvent's that you'll be dispatching.

Doing it this way allows your component's event dispatcher to be linked to the custom event through the submit detail.

In the emitting component:

<!-- Login Component -->
<script lang="ts" context="module">
    type ComponentEvents = {
        submit: SubmitDetail;
        // add more properties for each event you wish to dispatch
    };

    type SubmitDetail = {
        username: string;
        password: string;
    };

    export type LoginSubmitEvent = CustomEvent<SubmitDetail>;
</script>

<script lang="ts"
    import { createEventDispatcher } from 'svelte';

    const dispatch = createEventDispatcher<ComponentEvents>();

    const handleSubmit = (e: HTMLFormElement) => {

        e?.preventDefault();

        const payload: SubmitDetail = {
            username: e?.target?.username?.value,
            password: e?.target?.password?.value,
        }

        dispatch('submit', payload)
    }
</script>

<form on:submit={handleSubmit}>
    <label for="username"><b>Username</b></label>
    <input type="text" placeholder="Enter Username" name="username" required id="username">

    <label for="password"><b>Password</b></label>
    <input type="password" placeholder="Enter Password" name="password" required id="password">

    <button type="submit" disabled={isSubmitting}>Login</button>
</form>

In the consuming component:

<!-- Another component that's using the Login Component -->
<script lang="ts">
    import Login from "./Login.svelte"
    import type { LoginSubmitEvent } from "./Login.svelte"
    
    function handleLogin (event: LoginSubmitEvent) {
        console.log("Username:", event?.detail?.username);
        console.log("Password:", event?.detail?.password);
    }
</script>

<Login on:submit={handleLogin}/>

If you need to tie the handler and dispatcher together - you'll have to do it via exporting the callback function to be defined by the calling component, which you can follow Kevin Cox's example for... However, that doesn't answer this question. To decide on whether you should use custom events or callbacks, you can refer to this official discussion: https://github.com/sveltejs/svelte/issues/2323#issuecomment-569954265

License answered 23/2 at 3:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.