I'm trying to make a button component that will hide the button and display a spinner when it is clicked and is waiting for some asynchronous job to complete, and then hide the spinner and display the button after the asynchronous job is complete.
The key difficulity I'm facing is that I want parents to use on:click
in this way:
<ActionButton
on:click={someAsycnFunction}
>
Go
</ActionButton>
I do not want the parent to have to use a workaround such as:
onClick={someAsyncFunction}
Here is my ActionButton.svelte
child component now. While this successfully wraps the call to the parents someAsyncFunction
, I am unable to await the result of this.
<script>
import { createEventDispatcher } from 'svelte';
let isRunning = false;
const dispatch = createEventDispatcher();
// Wrapper function for the click event
function handleClick(event) {
console.log('child handleClick');
isRunning = true;
/* This doesn't work because the parent function is async
* and there is no way to await on this */
dispatch('click', event);
isRunning = false;
}
</script>
{#if !isRunning}
<button
on:click={handleClick}
>
<slot />
</Button>
{:else}
...
{/if}
The handleClick
successfully wraps the parent's call to someAsyncFunction
, however, I do not see a way to await the dispatch('click', event)
The result is that isRunning is set to false immediately instead of waiting for the parent someAsyncFunction
to complete.
Is there any way to accomplish this while still allowing the parent to 1) not have to know about the internal isRunning
and for the parent to be able to use on:click
instead of something like onClick
?
In Svelte5, my desire to use on:click
is void becasue Svelte5 uses onclick
instead.
So for Svelte5 the following is sufficient:
Button.svelte
<script>
let {
onclick = null,
children = null,
...restProps
} = $props();
let isLoading = $state(false);
async function onclickWrapper() {
isLoading = true;
await onclick();
isLoading = false;
}
</script>
{#if isLoading}
<Spinner />
{:else}
<button
{...restProps}
onclick={onclickWrapper}
>
{@render children()}
</button>
{/if}
And in the Parent.svelte:
<script>
import Button from "$lib/components/Button.svelte"
async function save() {
await new Promise(r => setTimeout(r, 1000));
}
</script>
<Button
class="btn btn-primary"
onclick={save}
>
Save
</Button>