Use actions on Svelte components
See original GitHub issueDescribe the problem
There’s some things I often find myself doing:
- dispatching events when component is mounted:
import { onMount, createEventDispatcher } from "svelte";
const dispatch = createEventDispatcher();
onMount(() => dispatch("mount"));
- dispatching events when destroying:
import { onMount, createEventDispatcher } from "svelte";
const dispatch = createEventDispatcher();
onDestroy(() => dispatch("destroy"));
E.g. I have one component WithShortcut
, which exports a let:
binding to create a shortcut, e.g. like this:
<WithShortcut shortcut="Control+C" let:createShortcut let:shortcutLabel>
<Button title={shortcutLabel} on:click={doThing} on:mount={(event) => createShortcut(event.detail.button)}>Click me!</Button>
</WithShortcut>
That’s quite the boilerplate, imo, and it leads to code which doesn’t feel good, as we’re using events, just to be able to listen to the lifecycle event outside of the component. Also, using on:
feels more correct, if you’re reacting to events, but what we’re doing here is extending the component, for which use:
would feel better.
There’s other issues here. The cleanup for the button happens in Button
, but the cleanup code for the shortcut happens in WithShortcut
.
Describe the proposed solution
What would be much nicer imo, is if we could use use:
actions on components.
The semantics would be similar to HTML elements: the action’smount
corresponds to onMount
, and destroy
corresponds to onDestroy
.
This means that the whole thing is basically just syntax sugar. I’ve built an example here, where I’m recreating a Component use:
action with props:
I’ll also copy it over here. <MyComponent use:action={param} />
translates to:
// MyComponent.svelte
<script>
import { onMount } from "svelte";
import { get_current_component } from "svelte/internal"
export let action;
export let param;
let elementUpdate;
function update() {
if (elementUpdate) {
elementUpdate(param);
}
}
$: param, update();
const component = get_current_component();
onMount(() => {
const { destroy, update } = action(component, param);
elementUpdate = update;
return destroy;
})
</script>
The above code could then be rewritten as (I know {@const
is not a thing yet, but it’s also a nice feature):
<WithShortcut shortcut="Control+C" let:createShortcut let:shortcutLabel>
{@const createButtonShortcut = component => createShortcut(component.state.button)}
<Button title={shortcutLabel} use:createButtonShortcut on:click={doThing}>Click me!</Button>
</WithShortcut>
See here for a REPL of how I wish I could do it.
I believe this makes it more semantic.
This would also be easy to teach new users, as it mirrors how they behave on elements:
<div bind:this={myElement} use:actionOnElement />
<Component bind:this={myComponent} use:actionOnComponent />
(This feature seems quite natural to me, and I was surprised to have not found this feature suggestion. If it has been suggested before, I’d appreciate a link.)
Alternatives considered
-
As I mentioned above, I currently use
onMount(() => dispatch("mount", { button }));
. See here for a REPL. -
One might say, I could import
registerShortcut
fromButton.svelte
and use it there directly, but I don’t want to teach every component that might get a shortcut how to create them, and eventually how to assign tooltips (title
).
Importance
would make my life easier
Issue Analytics
- State:
- Created 2 years ago
- Comments:9 (5 by maintainers)
I always thought of actions as mixins. Even the actions used in the tutorial could be aptly described as
Pannable
andLongPressable
.Yep, we experimented with this, but what we found is that you loose most of Svelte’s niceness like slots, and instead you’ll pass around deeply nested objects.
I think this sounds more like mixins than actions 🤔, not sure what others are doing in this space (you can get pretty far with
<svelte:component>
and passing component constructors around and spicing upprops
along the way).Now this is getting too complex for discussing this here and these type of architectural decisions require more in depth understanding than what I can provide here. I will say this: I personally would think of the extensions in terms of data, not in terms of UI elements. My UIs are data/store driven. The UI is just a way to visualize the data. Your data could flow through all of of the extensions and the extensions can make decisions (e.g. setting
visible
tofalse
). Like middlewares in a Connect/Express/Polka app. And the UI doesn’t even know about all this, it just updates with the current state and makes sure it’s consistent.