Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Why use createEventDispatcher? #2323

Closed
loilo opened this issue Mar 27, 2019 · 16 comments
Closed

Why use createEventDispatcher? #2323

loilo opened this issue Mar 27, 2019 · 16 comments

Comments

@loilo
Copy link

loilo commented Mar 27, 2019

I'm currently going through the v3 tutorial and I'm a little bit confused by the use of createEventDispatcher() to emit events:

import { createEventDispatcher } from 'svelte';

const dispatch = createEventDispatcher();

dispatch('event')

From reading the Svelte source, I'm aware that calling this function returns a dispatcher bound to that very component.

However, this feels weirdly verbose for how brief Svelte usually is. Is there a reason we can't (or shouldn't) just do this...

import { dispatch } from 'svelte';

dispatch('event')

...and let the compiler do the actual binding (e.g. directly after importing dispatch to the component)?

@Conduitry
Copy link
Member

I'm actually not sure - I was about to answer with the standard answer for several questions related to this (that runtime imports from 'svelte' are regular functions, and not treated specially by the compiler), but this doesn't seem to explain it in this case. The current implementation of createEventDispatcher is

function createEventDispatcher() {
	const component = current_component;

	return (type, detail) => {
		const callbacks = component.$$.callbacks[type];

		if (callbacks) {
			// TODO are there situations where events could be dispatched
			// in a server (non-DOM) environment?
			const event = custom_event(type, detail);
			callbacks.slice().forEach(fn => {
				fn.call(component, event);
			});
		}
	};
}

But it feels like we should be able to just directly use current_component right away without the extra layer here.

@loilo
Copy link
Author

loilo commented Mar 27, 2019

Maybe we should be — I'm not aware how current_component works exactly.

EDIT: Please ignore the following, I thought the implementation had callbacks, but it's actually all the same call stack.

Obsolete considerations inside

It looks like it's bound to component to avoid things like a suddenly different current_component in callbacks (e.g. setTimeout).

What I mean is this (note the code comments):

// If we replaced all `component` with `current_component`...
function dispatch(type, detail) {
	// ...are we guaranteed to have the same `current_component` here...
	const callbacks = current_component.$$.callbacks[type];

	if (callbacks) {
		const event = custom_event(type, detail);
		callbacks.slice().forEach(fn => {
			// ...and here?
			fn.call(current_component, event);
		});
	}
};

This looks like it would be an issue.

However, regarding the "standard answer": Is there an FAQ where this is written down, maybe with the reasoning why Svelte does touch a lot of stuff, but not this? 🙂

@Conduitry
Copy link
Member

Exports in 'svelte' are regular functions so that you can import them in other non-component files and encapsulate reusable bits of logic that can then be imported in other components and just work. Explanations of these types of decisions probably do belong somewhere on the site, but there's not a good home for them yet.

@loilo
Copy link
Author

loilo commented Mar 27, 2019

Alright, that sounds reasonable.

And I think my first considerations were actually correct:

import { dispatch } from 'svelte';

function sayHello() {
	// `current_component` might have changed by the time this is called
	dispatch('message', {
		text: 'Hello!'
	});
}

It still feels a little bit off though to use rather verbose constructs like createEventDistpatcher in the otherwise very terse Svelte code. 😕

@loilo
Copy link
Author

loilo commented Mar 27, 2019

However, this definitely clarified my question, thanks for taking care. 🙂

@loilo loilo closed this as completed Mar 27, 2019
@Conduitry
Copy link
Member

Yeah, you're right, it doesn't work. current_component will likely not still have the correct value by the time you call dispatch(), I'm not sure what I was thinking.

As for the terseness: It's now a lot easier to have props that are callbacks in Svelte 3, so you'll probably be using events less anyway. Having to create a dispatcher is a little verbose, yeah, but it does seem necessary given other goals and constraints.

@JohnnyFun
Copy link

Related: when would you recommend using createEventDispatcher over simply passing a function? For example, https://svelte.dev/repl/897e056bca05458ab6a4835991d0a788?version=3.16.4 (one component dispatches an event and the calling code retrieves data from event.detail, while the other component invokes a delegate if it's present).

@markhildreth
Copy link

@JohnnyFun I had the same question today, and have come up with what I hope is an answer. Hopefully someone can correct me if I am not quite right or missing something important.

By using createEventDispatcher, you are telling the compiler to compile your component in such a way that any events it emits adheres to the typical CustomEvent interface. This isn't that big of a deal if you are just dealing with Svelte, but I gather that if you are compiling Svelte components to be used elsewhere as web components, your compiled component will be compiled with an interface other frameworks will understand (i.e. it will have a correctly implemented addEventListener and removeEventListener methods).

If you aren't doing anything tricky like that, then the main benefit I see is easily letting the event of a child component be exposed from a parent. In your example, if you wanted to wrap Thing1 in a parent component Thing1Parent, that parent component can look like this:

<script>
</script>

<Thing1 on:click />

Now, Thing1Parent also has a click event that you can register to, which is fired when the Child is fired. If you used straight methods, it would look like this, which is a slight bit more verbose:

<script>
   export let onClick;
</script>

<Thing1 onClick={onClick}/>

Also, since these are DOM events, you can also use modifiers, such as:

<Thing1 on:click|once={doSomething}/>

From what I can tell, you can get away with passing methods through props. There are some small benefits to using the dispatcher, but it would be pretty much mandatory if you are doing something where your svelte components are used in a system besides Svelte.

@JohnnyFun
Copy link

Sure, I could see that being the explanation. Sounds like there are ways to pass function refs to web component instances, but I suppose people would also want to have a more common interface for their web components' events.

Fwiw, you could do <Thing1 {onClick} /> to make it almost as concise as <Thing1 on:click /> in Thing1Parent.

Also fwiw, I think once might be the only modifier that can be used with customly-dispatched events. I ran into that the other day, when I had a Btn component effectively forwarding the underlying button click event--I needed to stopPropagation of the underlying event. I just opted to use a button element directly instead of my Btn svelte component since I didn't need any of its special functionality in that particular case.

@bluwy
Copy link
Member

bluwy commented Dec 7, 2021

Coincidentally today, I've created a $dispatch utility that mimics the ideal API:

// dispatch.js
import { createEventDispatcher } from 'svelte'

export const dispatch = {
	subscribe(fn) {
		fn(createEventDispatcher())
		return () => {}
	}
}
<script>
  import { dispatch } from './dispatch'
  function foo() {
    $dispatch('bar')
  }
</script> 

https://svelte.dev/repl/ae1e9514c42845a4b3d30bb3af1168e4?version=3.44.2

@mcgrealife
Copy link

@bluwy wahoo!

@Valexr
Copy link

Valexr commented Dec 8, 2021

Coincidentally today, I've created a $dispatch utility that mimics the ideal API:

// dispatch.js
import { createEventDispatcher } from 'svelte'

export const dispatch = {
	subscribe(fn) {
		fn(createEventDispatcher())
		return () => {}
	}
}
<script>
 import { dispatch } from './dispatch'
 function foo() {
   $dispatch('bar')
 }
</script> 

https://svelte.dev/repl/ae1e9514c42845a4b3d30bb3af1168e4?version=3.44.2

what about unsubscribe?

@bluwy
Copy link
Member

bluwy commented Dec 8, 2021

what about unsubscribe?

unsubscribe is a no-op function since there's nothing to unsubscribe to. We're using Svelte's auto-subscription feature to call the subscribe() on component initialisation.

@michaelcuneo
Copy link

Because you might need data inside data inside data that is so far into an obscure part of an app that it isn't as simple as a quick, on:click do a thing in the other thing.

I've got a component that display or hides other components based on the variables within components that may or may not have been clicked in other components... it sounds like a ClusterF$&K but createEventDispatcher made it a breeze.

@nickyhajal
Copy link

nickyhajal commented Apr 29, 2023

Coincidentally today, I've created a $dispatch utility that mimics the ideal API:

// dispatch.js
import { createEventDispatcher } from 'svelte'

export const dispatch = {
	subscribe(fn) {
		fn(createEventDispatcher())
		return () => {}
	}
}
<script>
  import { dispatch } from './dispatch'
  function foo() {
    $dispatch('bar')
  }
</script> 

https://svelte.dev/repl/ae1e9514c42845a4b3d30bb3af1168e4?version=3.44.2

Curious... do you still use this? Found any issues? Seems like such a clever approach! @bluwy

@bluwy
Copy link
Member

bluwy commented Apr 29, 2023

Yep, it still works great 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants