sveltesveltekit

Svelte how to wrap an on:click event in a child and await the parents function?


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 ?


Solution

  • 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>