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.