Svelte TypeScript: Unexpected token when adding type to an event handler
Asked Answered
S

3

5

I'm trying to implement TypeScript into Svelte and has a problem like this: when I try to add type to an event in beneath line:

on:click={(e: Event) => onClick(e, data)}

it yells about:

Error: ParseError: Unexpected token

If I remove typing it says that:

Parameter 'e' implicitly has an 'any' type.

How can I add type to this kind of things without an error in Svelte?

EDIT: More complex example:

{#each elementsArray as element}
      <CustomComponent
        on:itemClick={(e: Event) => doSomething(e, element)}>
      </CustomComponent>
    {/each}
Sleight answered 10/8, 2020 at 9:49 Comment(0)
L
6

According to the official docs, there's no official support for TypeScript in the template.

At the moment, you cannot [use Typescript inside the template/mustache tags]. Only script/style tags are preprocessed/transpiled.

You need to move the typecasting to the <script> tag.

<script lang="ts">
  function onClick(e: MouseEvent) { ... }
</script>
<button on:click={onClick}></button>

In case your event is coming as a custom event from a child using createEventDispatcher, your e event will be typed as CustomEvent<any> and you can typecast it in the <script> as you please.

<script lang="ts">
  let elems = [1,2,3];
  
  function onClick(e: CustomEvent<{foo: string}>, el: number) {
    console.log(e.detail.name);
  }
<script>
{#each elems as el}
  <CustomComponent on:itemClick={e => onClick(e, el)}></CustomComponent>
{/each}

In case you still get errors for implicit any, try turning off noImplicitAny in your tsconfig.json.

Laconia answered 10/8, 2020 at 12:25 Comment(4)
But one question about that - what if I have some more properties to pass? Not only an event? Like on:click=((e) => onClick(e, someThing))Sleight
Where's that someThing coming from? is that a prop from the script? It seems to me like it's not coming from the template so I think you can just use someThing in the onClick function as a "global". It won't feel as clean, though.Laconia
Carlo, I've updated original post andd added some more complex code that explains what I'm aiming at. In that case don't know how to pass info about what element I'm clicking.Sleight
I've updated my answer to handle the more complex case. I'm not getting errors for e being any.Laconia
W
0

There are a number of bugs files on this issue, e.g. svelte/4701

Willdon answered 23/8, 2023 at 15:11 Comment(0)
M
0

Let me suggest some workarounds for this Svelte/Typescript problem.

First two basic variants:

let elementsArray = ["foo", "bar", "lorem", "ipsum"];

function doSomething(evt: Event, element: string) {
    console.log("doSomething() called", evt, element);
}

function callbackFactory1(element: string) {
    return (evt: Event) => {
        doSomething(evt, element);
    };
}

function callbackFactory2(callback: Function, ...args: unknown[]) {
    return (evt: Event) => {
        callback(evt, ...args);
    };
}

Then use the callback factories like this:

{#each elementsArray as element}
    <p><a on:click={callbackFactory1(element)} href="#">{element}</a></p>
    <p><a on:click={callbackFactory2(doSomething, element)} href="#">{element}</a></p>
{/each}

What happens is that the callbackFactory() functions are called when Svelte is setting up the component, and the functions that they return are then assigned as the event handlers.

The first one (callbackFactory1()) obviously has more type safety but is also less flexible. You have to create new callback factories for every callback.

The second one is more flexible but has less type safety. You can use it with any callback function and any set of arguments, but beware of type bugs!


Now, if we make this a little bit more complicated with currying, we can actually get both type safety and flexibility at the same time!

function curriedDoSomething(evt: Event) {
    return (element: string) => {
        console.log("curriedDoSomething() called", evt, element);
        // Either call doSomething() or implement logic here
    };
}

function callbackFactory3<TE extends Event, TA extends unknown[]>(callback: (evt: TE) => (...args: TA) => void) {
    return (...args: TA) => {
        return (evt: TE) => {
            callback(evt)(...args);
        };
    };
}

It may look complicated, but the gist of it is that we return a chain of functions; one that receives the event, and then another that receives "the other arguments".

By creating separate functions for these groups of arguments, we achieve type safety in Typescript. (Maybe it's possible some other way, but I was unsuccessful.)

<a on:click={callbackFactory3(curriedDoSomething)(element)} href="#">{element}</a>

The downside of this is of course that you must curry your functions, but the upside is that Svelte and Typescript will make sure that you're expecting the correct Event and callback arguments.

Microgroove answered 4/9 at 13:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.