Search code examples
javascriptsvelte

This `dispatch()` is completely ignored. Why?


I created a reproduction for an issue with Svelte dispatch not being called:

Steps to reproduce

  1. Open the reproduction

  2. Open the browser console

  3. Go on the "About" page using the link

  4. I sould see the message: "handleInput" but I'm not

Relevant code

<script>
    import Select from 'svelte-select';
    import { createEventDispatcher } from 'svelte';

    export let value = undefined;
    export let id = undefined;
    
    const dispatch = createEventDispatcher();
    
    let result;

    let items = [
        { value: 'one', label: 'One' },
        { value: 'two', label: 'Two' },
        { value: 'three', label: 'Three' },
    ];

    $: if (id !== undefined) {
        result = id;
    }

    $: if (result != undefined) {
        value = { value: 'custom', label: 'Custom' };
        console.log("this should dispatch!")
        dispatch('input', value);
        console.log("is it dispatched?")
    }
</script>

<Select {value} {items} on:change on:input />

The dispatch('input', value) is completely ignored.

Why?


Solution

  • Reproduction steps are:

    1. Navigate to 'about' page
    2. ✅ Observe handleInput not being logged to browser console (this failed successfully)

    Your reactive statements are firing before the on:input event listener attaches to the <Select> component. This may be due to the fact that reactive statements are called before the DOM (or component markup) mounts, and event listeners are attached after the component mounts.

    The reason your setTimeout code works is because it adds a microtask that likely registers after the event listeners get set up; so when its callback function fires, your on:input listener will receive the dispatched event. Similarly, using await tick() makes your code work for the same reason:

    async function dispatchEvent () {
      value = { value: 'custom', label: 'Custom' };
      console.log("this should dispatch!")
    
      // wait for all microtasks to complete before continuing
      await tick() 
      dispatch('input', value);
      console.log("is it dispatched?")
    }
    
    $: if (result != undefined) {
      dispatchEvent()
    }
    

    With that said, having many reactive statements is challenging to follow. I recommend refactoring to use event listeners in your CustomSelect.svelte, which can be combined with event forwarding:

    <Select on:input={internalInputHandler} on:input on:change />
    <!--    ^ handle input                  ^ forward input -->
    

    I also recommend dispatching events with a custom name, rather than override native event names; that way you can handle them separately and expect the native InputEvent for on:input. You could name your dispatched event input--id:

    <!-- Component.svelte -->
    <script>
      function handler() {
        dispatch('input:component', { some: 'detail' })
      }
    </script>
    
    <!-- +page.svelte -->
    <Select on:input--id={doSomething} on:input={doSomethingElse} />