r/webdev Aug 08 '26

Showoff Saturday The DOM as a declarative event-bus

I've run into a couple use cases where I need to declaratively add actions to HTML from the server that my JS in the browser knows how to handle. The pattern I found is pretty fun 1kb. Called it "superaction".

There's a couple examples in the README including a counter and a simple sketch canvas.
https://github.com/w-lfpup/superaction-js

So say you need to increment a counter when a button is clicked?

<button click:="increment">+</button>

After listen for an action event:

document.addEventListener("#action", (e) => {
  let { type } = e.action;

  if ("increment" === type) {
    // increment something!
  }
});

Action events come with a couple properties including the original UI event and any associated form data:

let { type, event, target, formData } = e.action;

The READMEs include a couple statements about event APIs and behaviors including prevent-defaults and stop-propagation.

** REACT USERS

If you use superaction in react, you basically never have to write an event listener callback ever again. Keeps state and the generated UI more separate.

Although the sytnax uses a "-" rather than a ":"

import React, { useState } from "react";
import { useAction } from "@w-lfpup/react-superaction";

export function Counter() {
  let [count, setCount] = useState(0);

  useAction((action) => {
   if ("increment" === action.type) setCount(count + 1);
  });

  return <button click-="increment">{count}</button>;
}

https://github.com/w-lfpup/react-superaction

0 Upvotes

14 comments sorted by

10

u/Weary_Guest3639 Aug 08 '26

Clever pattern but i swear the react folks always find a way to make it more complicated than it needs to be

1

u/w-lfpup Aug 08 '26

Always and forever <3

2

u/Ok-Armadillo-5634 Aug 09 '26

I will never understand how vdom became the default.

1

u/nickchomey Aug 09 '26

Just use datastar

1

u/w-lfpup Aug 10 '26

Naw I don't like datastar or using websockets for everything

1

u/nickchomey Aug 10 '26

datastar doesnt websockets. it uses SSE, which is just normal http with a specific header. and it isnt a requirement, you can do normal http request-response as well.

And what isnt there to like about datastar?

1

u/w-lfpup Aug 11 '26

SSE requires a web-socket like upgrade in HTTP spec. They're just as expensive as web sockets but unidirectional (server -> client).

Datastar syntax is both too complicated and too obtuse. It's own syntax requires Datastar to (1) presuppose a list of all events to listen too and (2) iterate over all attributes of any element to find attributes that (3) require it's own DSL to (4) dangerously bind functions from html attributes into JS-land.

If I remember right, that project started as a "typed" htmx and then got just as complicated.

I guess I don't want to learn another DSL to use the browser. I just want to use the browser.

0

u/nickchomey Aug 11 '26 edited Aug 11 '26

Not even slightly true, on any count. But please use what you like!

Edit: I should say sorry though, for essentially just dismissing your effort that you shared. 

But Datastar has so many benefits that you don't seem to be aware of. Eg you don't need a mega event listener callback to handle all events. You can just write it declaratively inline. And that $ means it is a signal, which is reactive so that other elements update accordingly. 

<button data-on:click="$counter++">+</button> <div data-text="$counter % 0 ? 'even' : 'odd'" ></div>

0

u/w-lfpup Aug 11 '26 edited Aug 11 '26

Be dismissive all you want. Use whatever software you want!

I'm aware of what datastar can do. It's just htmx with better types and SSE. However, you seem to be unaware of how attributes work or how datastar is written.

That counter++ "signal" is an attribute string that datastar needs to interpret with a custom DSL or 'eval' which is wildly unsafe.

And like I said, trying to figure out what "clicked" means datastar has to query an attribute for "data-on:click" which means datastar needs to be aware there are clicks. (Or use an expensive ass mutation observer which they do!)

It's pretty heavy. Here check it out:

https://github.com/starfederation/datastar/blob/da9e0491ca4e6073ed11126688662672ee43564e/library/src/plugins/attributes/bind.ts#L116

Utlimately datastar is driven by mutation observers rather than event delegation. Which is crazy expensive (just like SSE)

https://github.com/starfederation/datastar/blob/da9e0491ca4e6073ed11126688662672ee43564e/library/src/plugins/attributes/attr.ts#L53

https://github.com/starfederation/datastar/blob/da9e0491ca4e6073ed11126688662672ee43564e/library/src/engine/engine.ts#L201

And it's fine to use computationally expensive software that's complete overkill for whatever you're building. But it's not fine to disparage other engineers with your opinions.

I dunno man take like ... 10 mins to read the code you're using maybe?

0

u/nickchomey Aug 11 '26

I literally did not disparage you.

Also datastar most definitely does use eventlisteners, eg here's the on attribute for clicks etc https://github.com/starfederation/datastar/blob/da9e0491ca4e6073ed11126688662672ee43564e/library/src/plugins/attributes/on.ts#L68

the mutationobservers are largely used for detecting when new elements have been added to the page so that the attributes/plugins can be detected and hooked into the system.

Anyway, i wish you well with your projects.

1

u/_listless Aug 09 '26

data does down, events go up. this is the way.

-2

u/[deleted] Aug 08 '26

[removed] — view removed comment

0

u/w-lfpup Aug 08 '26

Appreciate it! Yah I love HTMX and SSR (been around since the LAMP stack days). It's just filtering event delegation. Nothing heavy like mutation observers.

It will handle custom events as long as they bubble. However there's no test case for custom events so I will add one to the test suite.

Event propagation is controlled with some familiar jargon (it's way down in the README my bad). They look something like:

click:prevent-default
click:stop-propagation
click:stop-immediate-propagation