14 comments

[ 2.8 ms ] story [ 45.8 ms ] thread
Event capturing and bubbling and much more
I often imagine state and events as the two impulses that drive an application. I like React a lot, but a common pitfall is that it is 95% focused on state, and so you get odd cases where you end up trying to encode events as state.

You’ll see this anywhere you see a usePrevious-like hook that you then use to determine if something changed and act on it (eg. I hold state that a robot is offline, but I want to do something special when a robot goes offline). This is inferring an event from state.

I’ve had luck adding an event bus as a core driver of a complex react application for events I don’t want to track as state. But it always feels that it’s a bit in conflict with the state-driven nature of the application.

Using Svelte and building global state classes with $state(), $effect() has really helped with managing side-effects and surgical updates without building a custom event system which has historically added unnecessary boilerplate to many of my projects with a frontend.

Having components bound to or using any of the $states or $derived update automatically without having to manually register event listeners, firing events, etc.

Used to dislike runes so much initially, but working a bit more deeply with them has really made me appreciate the API changes.

One of my favorite quotes coming out of the Svelte team with respect to runes[0]:

    > Like every other framework, we’ve come to the realisation that Knockout was right all along.
    >
    > Svelte 5’s reactivity is powered by signals, which are essentially what Knockout was doing in 2010. 
(Knockout was fantastic and easy to adopt because it felt like it "closed the loop" with events and simply made them more ergonomic to use)

The problem is actually React hooks which has created a paradigm that inverts this and is now the most dominant model. This inversion of the model of how reactivity works is the root cause of most of the pain and complexity with React.

[0] https://svelte.dev/blog/runes#Signal-boost

I think events are a bit unsung and underutilized in a lot of web projects. Events are really powerful and you can build systems with them that can replace proprietary framework features with interoperable protocols.

Context: Components that need a context value can fire an event to request it. Any other element or listener higher in the tree can handle the event and provide a value via the event object. Event are synchronous, so you can get values synchronously. The Web Components Community Group maintains an interoperable context community protocol: https://github.com/webcomponents-cg/community-protocols/blob...

Suspense: Components that have some pending some work, like awaiting data to render, can fire an event to signal that they have pending work. The event can carry a promise, and then a suspense-boundary-like component can handle the event and display a spinner until all the pending work in the tree below it is finished. Another protocol: https://github.com/webcomponents-cg/community-protocols/blob...

Error boundaries: A component can fire an ErrorEvent if it fails to render, and an error boundary component can display the error or some other user affordance.

Child-parent registration: A child component can fire an event to tell some parent that it's available. This is useful for using components as plugins. A <code-mirror> element could have children that provide language support, syntax highlight themes, etc.

Actions: Redux-like actions can be done with events instead. You can build a nice data-down, events-up system this way with very little code and very loose coupling.

Event buses: components can listen for events on a top-level node like document, and they'll receive every event of that type from every other dispatcher.

Why is this so weirdly prescriptive about inline event handlers?

> Even in a single file, inline event handlers are not a good idea. One button is OK, but what if you had 100 buttons? You'd have to add 100 attributes to the file; it would quickly turn into a maintenance nightmare.

> You should never use the HTML event handler attributes — those are outdated, and using them is bad practice.

It’s a really good explanatory text, and then get surprisingly opinionated.

Similarly, why is an online event handler considered a security risk? I just don’t see the difference between that and using a named function?

Inline handlers could execute trusted code without user input but in a way that was unintended like this button that hijacks a method of a trusted library and disguises it behind a like button:

  <button onclick="trustedLib.confirmDeleteAccount()">Like</button>
This should be sanitized when the button html is injected into the DOM but CSP provides complementary protection, [1], if sanitizing fails.

[1] https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP...

edited: tried to fix formatting

>Why is this so weirdly prescriptive about inline event handlers?

>> Even in a single file, inline event handlers are not a good idea. One button is OK, but what if you had 100 buttons? You'd have to add 100 attributes to the file; it would quickly turn into a maintenance nightmare.

What's so weird about this? This practice, known as event delegation, has been a known Good Practice for decades. One event listener attached to a common ancestor element of 100 buttons is more memory efficient than 100 event listeners. Even jQuery, which makes it temptingly ergonomic to give 100 buttons an event listener each, prescribes the event delegation pattern in their documentation.

Edit: Sorry I just read the section; now I'm dismayed the MDN article is not prescribing/recommending event delegation. However I did find other MDN articles explaining it:

https://developer.mozilla.org/en-US/docs/Learn_web_developme...

https://developer.mozilla.org/en-US/docs/Learn_web_developme...

Protip: Make your web SDK APIs EventTargets instead of creating custom event subscription models wherever practical.
You can think of any software (or hardware) as a state that transitions to a new state when an event happens.

State0 -event0-> State1 -event1-> State2 etc.

In other words, it's all a state machine.

I dropped all state except local state in react and replaced it with events.

Driving UI declaratively via prop changes sounds great in theory but it’s actually a nightmare. Better to use events, dramatically simplifies things.

> Event handler properties have disadvantages compared to addEventListener(). One of the most significant is that you can't add more than one listener for a single event. The following pattern doesn't work, because any subsequent attempts to set the property value will overwrite earlier ones:

That's not really a disadvantage, rather it makes it easy to change the event handler.

When is it ever necessary to change event handlers?
What the MDN documenation doesn't make clear, is the relationship between Inline Event Handlers and Event Handler Properties

<div id="DIV" onclick="console.log(1)">CLICK!</div>

<script>

    DIV.onclick = (evt) => console.log(2)

    DIV.addEventListener("click",()=>console.log(3))
</script>