I know the HTMX guy has opinions on this, but I also like how React (and similar frameworks) give you locality of behavior.
<button onClick={() => fetch(‘/clicked’)}>
To me that feels like less magic to remember than
<button hx-get=“/clicked”>
But ultimately let’s be honest: Experienced engineers can write good code in anything and inexperienced engineers can write bad code in anything. Simplicity takes time.
But...your code examples are not doing the same thing though. The React example just does a `GET /clicked` request and discards the response. The htmx example does a GET request and swaps the response into place, replacing the button. This is analogous to an `<a>` or `<form>` tag that makes a request, gets the response, and then replaces (ie swaps) the page.
Try making your React example do what the htmx one does. That's when you'll see the complexity start to creep in.
But how often do you actually need that when writing a reactive app? In most cases I have worked with you want the button to persist and load some external data that updates a reactive state.
Sure, try doing whatever equivalent makes sense in React. Then try comparing the complexity of the two approaches. The point is to compare apples to apples as much as possible.
> Experienced engineers can write good code in anything and inexperienced engineers can write bad code in anything.
I don't have strong opinions about this particular React/HTMX example, but I don't love this statement because it seems to be dismissing the possibility that some tools are better than other tools for a particular job.
It is exactly dismissing the possibilities some tools are better than others for particular jobs, and it has been a popular phrase for people advocating for bad tools for decades.
The previous favorite one used to be "a bad craftsman blames his tools", that people that liked to impose bad tools on others repeated all the time. But that phrase got old.
> it seems to be dismissing the possibility that some tools are better than other tools for a particular job
I think these are 2 dimensions. Great engineer + right tool is obviously the best combination. Great engineer + wrong tool can work surprisingly well. I have never seen the other 2 combinations produce elegant results.
But really what I was trying to say is this: We're just smearing complexity around. UI is hard. The component model won for a reason. Use whatever syntax you prefer, they're all fine, but please use composable components of some sort to build your UI.
Eh, not really. Htmx is a tool that you can plug in to your system. It doesn't dictate what system or architecture you use. You can have a component model, or you can have old-fashioned string-based HTML templates. Htmx is agnostic to that.
I worked on a system (Themis) for creating ML training sets for big corporations which would basically show a screen that has the user make a judgement and then show them another screen and so forth.
We had to develop this thing at a breakneck pace and if it was successful, I'd have the expectation that we (or the customer) would always be developing new models and new tasks.
As it was this system had a React front end and a "microservice" back end in Scala. Everything was packed up and deployed with Docker. The engineering manager didn't believe me when I said that it took 20 minutes to turn around the smallest change in this system so we timed with it with a stopwatch and we measured 18 minutes. Notably if you want to add a new task you have to change more than one back end process and update the SPA. You had to be a skilled front end and Scala developer to change things yourself and if you're working on a team you are going to have a lot of back and forth which slows things down.
I had a counterproposal (Nemesis) that I've been using for Centaur processes [1] that I develop on my own account which is an old-school site enhanced with HTMX.
A task is implemented in a Python file using the flask API with a class that has a few flask methods for HTTP endpoints and a small number of Jinja2 templates which draw the HTML. [2] Adding a new task is a matter of adding a new class and a new template and deploying it is super-easy, turnaround on a flask server is instantaneous, it's just a few seconds with a production gunicorn server. It takes less programming skill than Themis, a new task can be created by cut-and-paste-and-modify or be vibe coded without risk of breaking other tasks.
Since it doesn't have lots of images, chrome, Javascript, metadata in the <head>, ads, trackers and crap it is crazy fast. On my tablet, going the wrong way on an ADSL connection, over wireless and my phone's hotspot, it feels like using a desktop application. The app has plenty of screens that do visualization with d3.js and other things with Javascript where necessary. If I had a task that required a very complex interaction then I could write an tiny SPA just for that task with React, Svelte or whatever I wanted.
[1] Human processes some tasks, AI processes some tasks, AI gets trained based on human performance
[2] The exact same architecture would work in Java with JAXB
How does that follow? Or are we talking past each other?
I tried to model this formally (well, to the extent I can model anything formally), cause this is a fun little exercise in a way, and got that at least as far as my toy model goes, it's a wash, both matter equally (since at the extremes, they will approach each other's distributions, i.e. the distribution of expertise or the distribution of tool choice).
Or to be more specific, what matters more is what you can change more, something I only accounted for in the very end. And I don't think significantly pushing the needle in expertise, especially in a way that persists over time, is realistic. People need time to gain expertise, so those with majority expertise will always be a minority. But improving tooling and doing so with long lasting effects is much more realistic. Hence, good tooling that leads your hand matters more. It raises the bar, and automatically ensures past mistakes aren't repeated without a need for costly rediscovery or specific training.
Think of frameworks as "distilled experience". They help the next "generation" go faster because they don't need to make all the same mistakes. This is awesome.
But it is not magic. People will make a mess. Often because they hold the framework wrong and/or miss what it's trying to do for them. Or just don't grok the author's beautiful vision.
However, an experienced practician groks the fundamentals and can quickly adapt to any framework. Because all the frameworks are built on similar fundamentals so it's easy to hold them correctly. You go "Ah, the author believes X, so if we just assume X, it all falls into place and the code looks great".
That said, you might be working in a domain where X is fundamentally untrue. Then this is the wrong framework and an experienced practician will go "This is the wrong tool and choose a tool that fits better". The thing that doesn't fit may be the current team/experience moreso than the business domain.
At the end of the day more experienced engineers will produce a better solution than less experienced engineers. For a variety of reasons. Good tools can make less experienced engineers ramp up faster.
> That said, you might be working in a domain where X is fundamentally untrue. Then this is the wrong framework and an experienced practician will go "This is the wrong tool and choose a tool that fits better".
Agreed, this is always the money shot. I further think that the holy grail for advancements is for them to lead people's hands in a way that is self-justifying. It's a very tall and cruel order, but I think it's absolutely essential. If a solution fails to do this, people eventually turn on it, and adoption reverts or otherwise falls apart. I'm particularly worried about this happening to e.g. Rust, but this can be applied to basically anything.
Htmx does work for simpler use cases like submitting a login form. Beyond that it gets messy very quickly as you start introducing more backend endpoints for every little interaction. In some stacks you have template fragments[1] which alleviate this problem somewhat but still, htmx doesn't scale for more sophisticated interactivity.
And most projects will still need client-side interactivity. So now your features are a mix of htmx stuff, something client-side (Alpine, Vue, whatever), and probably some HTTP endpoints to interact with the client-side stuff.
You also still need to take care of CSS which htmx completely ignores because it's really just a low level HTML exchange protocol if you will. With Vue, Astro, or Svelte you can encapsulate markup, behavior, and styles in a single file.
And on top of all that, the DX is quite frankly terrible compared to doing frontend with something like Vite with hot module reloading. Most backend servers need to restart and maybe even recompile the whole thing. PHP is the only exception I know of since every request "runs the whole application".
I feel that as software engineers, instead of talking about things like 'feels like magic', we are capable of reading the docs and understanding what something actually does, especially when it's pretty simple: https://htmx.org/attributes/hx-get/
That's because lit templates are in template strings, so passing through a function requires the dollar sign and curly braces (that's just vanilla js). Everything else about it in the example, aside from using @click instead of onclick, is identical to react.