Hacker News new | past | comments | ask | show | jobs | submit login
Htmx vs. React: A Complete Comparison – Semaphore (semaphoreci.com)
59 points by kiyanwang 10 months ago | hide | past | favorite | 84 comments



Any time someone shows only HTMX examples without error handling I feel like they have not tried it in an actual use case. To me it feels non-obvious how to handle these non-happy-path things without having to write client-side code which is what I understood HTMX promised to do away with.

In the traditional browser model that HTMX espouses to emulate and improve on we have form resubmitting (with a warning that it will resubmit data), we have different errors for if the server returns an error or if your wifi disconnected, etc. Those errors are perhaps not well designed, but they are there and explain what is happening. With HTMX (at least when I tried it) it just got swallowed and one had to write client side code to handle it.


I think it's a little more accurate to say that htmx lets you handle the common cases with client-side attributes, so that you only have to bust out the client-side scripting in rarer cases.

You're definitely correct that the htmx documentation and tutorials and such don't talk about error-handling as much, and that should change! This is partly a function of how relatively young htmx is, and how long its been since the industry focused on hypermedia-related error handling patterns, so I'm optimistic this won't be the case for too much longer.

In the meantime, what I usually do is write a tiny htmx config snippet that intercepts 4xx or 5xx responses, and inserts them as a modal/popup/alert in the DOM. You can also do the opposite, and hook into an event from before the request, and do something with it if the requests fails for other reasons. Another common pattern is to return an error modal from the server and target the "error" spot in the DOM. There are also extensions for handling different response codes differently.

But again, while it's very do-able and I think reasonably intuitive once you start building larger apps with htmx, it's still an undeveloped aspect of the educational materials :)


To be fair most framework and library examples or showcases never show the error handling or edge case pieces so I don't think it's unusual.

htmx doesn't promise to do away with client side code. Most uses of it I see still have client side code. They tend to go in one of two directions with it.

1. Web Components to encapsulate client side error handling and behavior.

2. The hx-on attribute for scripted event handling.

It's not really an anti Javascript library. It's more of a different approach to server side state synchronization.

I have to dynamic apps I use for personal usages. One is an offline first spa app so htmx won't work well for it. It's all client side code with json apis. The other has no offline use case and htmx is a great fit for it but I still have several hundred lines of vanilla javascript in there for client side behavior bits.


HTMX does not promise to "do away with" client-side code. In many cases, you don't need or need significantly less JavaScript in an HTMX codebase than in other alternatives, but the point is not to use no JavaScript, but rather to extend HTML to do what JavaScript shouldn't be needed for. Its error handling story could be better in a number of ways, but it isn't a promise break.


I couldn't agree more. Without showing specific approaches for handling errors, there is too much uncertainty around whether or not htmx is actually a good approach. It doesn't matter how rare an error is. They happen and have to be addressed.

What happens, for instance, if the the request times out?

Also, the main reason, I feel, for actually wanting to use react is if you have an application with data that has a complex relationship with the UI. I don't know the technical term for this unfortunately, but the idea is that state management through, say, redux allows data changes to propagate throughout the UI. HTMX doesn't appear to address this at all. In fact, it seem like very brittle relationships between htmx elements are established. It is equivalent to a jquery + id nightmare.


> What happens, for instance, if the the request times out?

htmx triggers an event: https://htmx.org/events/#htmx:timeout

Then you, the developer, optionally handle that event and show a message to the user (if you want).


How errors are handled is often pretty specific to each use case.

Sometimes it does make sense to send back an HTML fragment with an inline error message, or a fragment that gets injected as a toast notification. Other times you may want to redirect to an error page with a full page refresh. Depending on the HTTP request method used, you may be able to return an HTTP error response too.


Absolutely. My point is that most or almost all HTMX examples/tutorials/tests show no error handling at all.

Sooner or later you need to handle these different errors, and I don't think HTMX has these sort of "non-happy" paths at mind.


Go to https://htmx.org/reference/#events and find in page 'error'. You will see all the different error events that htmx lets you hook into and handle. Don't be fooled by its apparent simplicity. It's very carefully designed.


Where is this promise on the htmx website?


There are different sorts of errors with respect to htmx:

1) application errors (e.g. bad data in a form)

These should be dealt with the "normal" way: rerender the form with error messages on them. Most good server side frameworks w/ form integration make this easy, and you can see an example of that here:

https://htmx.org/examples/inline-validation/

2) server errors

These are things like 500-level errors, 404s, etc. In this case htmx does not swap by default, but you can modify that behavior if you'd like to have it swap (and retarget, etc.)

3) network errors

On network errors, htmx triggers events such as htmx:sendError, that you can handle and show an error message, etc.

By and large we get almost no support issues around error handling: it is fairly obvious how to make things work if you are familiar with traditional web applications and more complicated things like connectivity issues are handleable via events, which isn't complicated either.

One thing that does occasionally bite people is when server-side frameworks respond with a 422 response code for application-level errors and try to rerender a form with those error messages. By default htmx will not swap that response since it is not a 200-level response code. You can modify this behavior here:

https://htmx.org/docs/#modifying_swapping_behavior_with_even...

If I had it to do over again, I would consider making 422 responses swap by default.

Regarding the "resubmit" warning, that doesn't apply to htmx-powered applications because AJAX operations don't naturally end up in the history chain. This means that the Post-Redirect-Get pattern isn't needed.

Error handling isn't a particularly hard aspect of htmx. What is hard is adjusting your mindset to the hypermedia approach, particularly if you are very deep into reactive programming. And there are applications that this approach is not suited for:

https://htmx.org/essays/when-to-use-hypermedia/

However, when your application is well suited for the approach, htmx (or similar approaches) can be a very big win:

https://htmx.org/essays/a-real-world-react-to-htmx-port/


For web apps, you can't use react without react-dom. That adds 131kb. (for react-dom.production.min.js@18.2.0) Very misleading to say react is 6kb in weight.


Presumably they’re using preact for a fairer comparison.


Using preact seems like a fair way to compare preact to htmx. Using preact does not seem like a fair way of comparing htmx to react. That's notable, because that's the title.


React weight 6.4 kB? In which universe?

React-dom alone is 42kB gzip.

https://bundlephobia.com/package/react-dom


Preact is 3kb and works as a drop in replacement for React (most of the time). It's also way faster.


Similar to how the size of react without accounting for react-dom is deceptive, one is also unlikely to use preact without @preact/hooks and/or @preact/signals which increases the size somewhat. Still much, MUCH smaller than react though.


Is there a reason to use the original react, or will most libraries also work with preact, which is both faster and smaller?


React and HTMX aren't addressing the same problem domain. This comparison is about as meaningful as any could be under that constraint.


Why is "dynamic web app without page reloads" not the same domain?


That's certainly part of the domain of react applications. But there are a lot of react applications that aren't (really) in the domain of HTMX, like "local first" applications or other applications that rely heavily on browser js APIs.


Absolutely, but there's a huge overlap in terms of what react and htmx can serve. Most websites that use react, can just as easily use htmx.


It's a little like comparing HTML and JavaScript, isn't it? React is a JavaScript library (in the sense that you write your own code that calls React functions). Whereas HTMX doesn't integrate with your application logic at all. It provides a mechanism for placing your application components on the screen.


HTMX relying on a server request to make even minor UI changes (e.g. their first example https://htmx.org/examples/click-to-edit/) is wild to me.

You might remember when a thing called DHTML revolutionized how JavaScript could be used to build interactive UIs on the web... why go back to pre-2001 era?

I get it, SPAs are overkill, but swinging the pendulum all the way back is very strange.


https://htmx.org/essays/hypermedia-friendly-scripting/

https://hypermedia.systems/client-side-scripting/

I can understand the objection to the first request but the second is necessary to update state on the server. You could avoid the first request via scripting but the complexity, in my opinion in most cases, would not be worth it.


Agree on the first point, I think using JS makes more sense when you need to update some client state, like a dropdown or even that edit example. But htmx makes a lot more sense when you need to render some data from a server. In a SPA, you receive JSON, only to transform it back into HTML to render, so why not skip that middle step?

You might find https://data-star.dev/ interesting, it takes some of the modern approaches (signals, etc) and mixes it with the efficiency of sending straight-up HTML, htmx style. It's very similar to using Alpine + htmx together.


I've been seeing more lately that the stack is being mentioned as htmx+alpinejs.

Not sure how long it'll be until the htmx part gets rationalised out in those instances. Maybe it won't, and people will keep shipping both. Stranger things have happened


htmx doesn't require you to make a request for UI changes. You can express your UI in terms of components which have built-in interactivity, eg dropdowns, inputs, details/summary tags, etc. You can go pretty far with those before you need to make a request.


What's a good middle ground for you? I don't intend to be combative at all, I just mostly hack on backend professionally and for fun so when it comes to evaluating frontend technologies I'm useless.

Is it preact? Svelte?


You can make a reasonable interactive app with htmx + javascript, but tings will be rather barebones -- you'll need to invent some patterns outside of what HTMX prescribes.

With react you get somewhat prescriptive low-level patterns when it comes to "managing data flow", so I would probably use react to keep things consistent.


As much as React is too wordy and bloated these days, this comparison seems extremely unfair, bordering on biased.


As a backend engineer I Still haven’t managed to learn how react data management or JSX works. Every time I try to dive into it I get a strong migraine. I hope something easier comes popular before I would need to learn FE


JSX is just one-to-one syntactic sugar for a createElement call (https://react.dev/reference/react/createElement). You don't even need to use it with React, any library that defines an identical function interface will do.


> one-to-one syntactic sugar for a createElement call

Used to be, but you are right that JSX is a rather thin syntax over function calls. (The modern JSX transform now uses a _jsx and _jsxs call, depending on whether you are rendering a single child or an array of children).

See https://legacy.reactjs.org/blog/2020/09/22/introducing-the-n...


i just struggle with hooks, data management, how data flows in between components etc etc


I did also, many coming from classic programming that hasn't touched upon functional ways of thinking usually struggle since they're prone to shortcuts whereas React really likes you to keep everything as transformations (or as some would call it functional style).

Much React work these days is confined into components, people sadly over-do it also because they perceived Redux as overkill and try to shoehorn entire applications into core React hooks (that are mostly designed for smaller components or at most views).

This is needless when Redux (with Redux-toolkit) has recipies that solves the whole-app dataflow problems well (people have issues with Redux because their components got overly complex in the past by shoehorning component issues into Redux instead and now they're complicating things in the other direction).

First rule of not messing up React/Redux: Don't mutate data, 90% of messups I see with fresh people is breaking this. Everything should become transforms! To accomplish this the spread operator (...) in JS/TS along with map/reduce/filter are your biggest friends. (You can do optimizations but 98% of the time you won't work on big enough datasets that you need to do that)

Thus when you do setX (of state hooks) you set your transformed data, same with sending messages in Redux, your Redux reducer acts upon a message to transform the internal state.


Transforming mutations into react-compatible object creation is not always trivial, particularly with twisty object graphs. I once spent a week trying to untangle a reducer monstrosity that wished it could mutate. In the end, I don't think I totally fixed it. It can be hard.


Not sure username checks out? :P

Joking aside, you're right, often it's almost easier in recursive cases to just go with flat object lists and keeping identities on objects and keep parent id's around.

In my view, React is a theoretically beautiful piece of software, and if you're planning on building a synchronized networked system keeping in line with what makes it beautiful will give you it almost for free.

For many practical cases (especially smaller SPA's/pages) however it's probably better to just go with Vue or similar and mutate away and let the system handle all update checks for you instead.


Hooks making things complicated, before that it's actually simple, let's try to talk with class-based component.

There's only two kinds of data inside a component (class-based), first is props. Props is, similar with html attribute, are given to the component by the caller / owner / parent. `<input type="password"/>` has type as the props key, and "password" as it's value.

State is data that lives inside a component and cannot in any way accessed by the caller / owner / parent. It can only be accessed by the component itself, or it's children if the component decided to pass it. In general, you'll use very few components with state in a single application unless you know what you're doing with it.

So about the data flow, generally data is handled (can be via state or state management libs, see later section) by higher ups (parent) component, and passed to it's children via props. Now the parent component, together with the the value passed, also pass function. The function can be called by the children to update the parents state.

As for data management, there's 2 way I usually handle it. First is using state management like mobx and passing it via several ways, usually using react context. State management libraries usually has section on this.

Second is to declare the data I needed on top most components (that's relevant to the data itself) via react context, and pass them either via props or make children access them via react context.


I just learned React/Nextjs for a take home coding interview assignment. I used useState a few times. Why would I need redux when there’s useState and useContext?

Other than being overly verbose, I think React is ok after building my website with Sveltekit. There just seems to be way more support for off the shelf react components and 99% of jobs ask for React.


Don't use redux alone, use redux toolkit or mobx which are more simpler and newer. Sure, you don't need them and can be replaced with useState instead.

They shine when you need / can separate the business logic with ui. Think about state management as "backend" for your frontend.

What props they have, whether a function will change which props, etc, are belonging to state management. Heck, even the state management can be done in separate project by different team.

The best thing, in a bigger org / bigger project, you can have 2 state management code. One is mock, for frontend to develop, which has no dependency with servers, and another is the real one, that really call the backend server for real data.


You are not alone!

I found it useful to actually work through the React tutorials.

Keep things simple and learn the simple use cases first.

The world of React is now a very busy place, so trying to jump in to a full stack can quickly become overwhelming!

FWIW I'm not a fan of how many React and GrpahQL frontends are built. For me it's a lot of complexity for little gain and plenty of pain!


Data flows top down, from parents to nested children, either explicitly via props or made available as context by parent providers. If data needs to be sent upwards, the parent component must send down a callback which the nested component can call.

Hooks are frankly something that you just have to put work into learning though as they can’t be succinctly explained. It’s worth the effort though.

Where ordinary programming paradigms result in tangled and messy state, and pure functional paradigms try to pretend state doesn’t exist and stick it all in a monad, hooks provide a framework to handle state in a way that composes much like pure functions do.


React `createElement` is not the same as `document.createElement`. The exact behavior of react elements still has a lot of complexity IMO. A react element is not analogous to a DOM element. It's more like a factory for creating a DOM elements. You can put the same react element in a document twice, no problem. That doesn't work for DOM elements. That's just the tip of the iceberg.


This is because you define what you want the components result to be. But not how to do it.

The return of a component render is a description of more work to do. When that’s done is up to reacts scheduler.

Once react has rendered a tree it then commits it to the dom.

Manipulating dom elements at runtime is imperative work. Doing it server side you get the same declarative capability as react, but no runtime behaviour.

React elements not being 1:1 with the DOM is what enables us to also target native, tuis, and so on.


> As a backend engineer I Still haven’t managed to learn how react data management or JSX works.

What are your specific pain points?

JSX is HTML-like syntactic sugar, just write normal html but put JS stuff in curly brackets.

Data management can be tricky at first, but as long as you remember that everything that isn't memoized (via e.g useState) is recomputed on every render pass, you're 90% there.

You can get into the reeds of it of course, but you really don't need to if all you want to do is just build a regular app.


Most frontend engineers don't actually know how it works either, if it makes you feel any better.


I'm not a frontend guy but everything seems more complicated by the fact that Object.observe[0] was ditched. Although there are good arguments against it.

[0] - https://caniuse.com/object-observe

[1] - https://www.bitovi.com/blog/long-live-es6-proxies


Now imagine how bad pre-react era frameworks were if the main selling point of React was “your SPA is now easier to reason about”.


That was a selling point of all the prior (and subsequent) frameworks as well. In fact, that's the selling point of htmx over react too. Opinions vary about how well any of these things achieve that objective.


Before switching to React in 2015 I worked with ExtJS and Backbone, and both of them were much harder to reason about. Oh, and GWT too, that was a nightmare.


I guess different people have different affinity and skills for reasoning about different things. If I knew I'd have to work on react for the rest of my career, I'd find a different line of work, specifically because of how difficult it is for me. Things I've found easier to understand than React include AlpineJS, Solid, Vue, Knockout, and vanilla imperative JS. The only thing I recall ever finding more mind-boggling than React has been Sharepoint-as-an-application-platform.


Have you looked at htmx? Or even full vanilla?


htmx better i generally just use vanilla or jquery, htmx it's still bearable


what about jsx? i find it an amazing mix of html and js

and what kind of data management do you mean? for a SPA most people use react query in place of redux these days, otherwise with something like remix you don't need any external data lib


I’m sorry to be a naysayer… but you can’t say React is a better performant or higher scale when the user experience down right stinks. There are a few exceptions, but it is overused to the point of detriment of the entire web so some developer can scratch an over engineering itch.


I think the list of features says it all. They're not exactly comparable, are they? I don't think there's even anything preventing you from using both with e.g. React on the backend and Htmx on the frontend to load in the HTML that React generates.


React on the backend + HTMX on the frontend would be hilarious. Someone should write a tutorial on this pretending to be serious.


Actually someone is already doing this. React components on the backend, rendered to static markup, sent over the wire to the frontend, which uses htmx. Someone tweeted about this recently.


WordPress just implemented the interactivity API, inspired of Alpine.js, using parts of Preact. It's really great and extensible, 10 kB only.

https://wptavern.com/interactivity-api-prepares-for-its-offi...


htmx is not 2k, sadly it is 15k:

https://bundlephobia.com/package/htmx.org@1.9.10

the authors are confusing it with another package:

https://bundlephobia.com/package/htmx@0.0.2


React is also not 6.4kb, it's 316kb for base React + 4.5mb for React DOM + who knows how much for any other library you decide to use.



I presumed, but either way you still have to consider that React's "6.4kb" is without any of the actual building blocks a web app will need, just the absolute bare minimum of what React itself needs.


Comparing a view library to a full front-end framework feels...weird. Even granting this stretch, it makes bizarre claims:

> Goal: Add modern interactivity features directly in HTML vs Provide a component-based, full-featured UI JavaScript library

Ah, no. Anyone who's used React.js should know otherwise. React is a declarative view library. It doesn't even handle routing or app state management, let alone the requirements for a "full-featured UI."

> [React] Features: [...] one-way data binding, state management

These statements are between "weirdly put" to "outright wrong."

> Learning curve: [Htmx] Gentle v. [React] Steep

React is dead simple. Your `HelloWorld = () => <button onClick={console.log}>Hello!</button>`. The learning front-end development can certainly be steep, but React is light-years away from kitchen sink frameworks that have you fiddling with yet another templating engine, controller classes, etc.

> React: A full-featured JavaScript library for building user interfaces based on reusable components written in JSX

This conflates React, ReactDOM, and JSX.

> Because of its unique approach to web development, React has a steep learning curve. Before building your first React application, you need to understand the concepts of SPA (Single Page Application), Virtual DOM, JSX, state management, props, re-renders, and more. This may overwhelm some beginners.

You don't need literally any of this aside from `props`. If you do use JSX, then its syntax will feel far more familiar than `hx-get` or whatever.

> SPA applications built in React usually contain a lot of JavaScript. That results in higher network utilization and client-side rendering times.

This conflates React with frameworks like Create React App. It ignores alternatives with SSR like Next.js. Also: don't encourage people to make more SPAs with React. Please.


> It doesn't even handle routing or app state management

Doesn't it? [1]

> another templating engine, controller classes, etc.

Template engines are easier than React, considering it's generally as easy as {{ and }} inside html.

Controller classes? It's as simple as print('<html><button>Hello!</button></html>')!

> If you do use JSX, then its syntax will feel far more familiar than `hx-get` or whatever.

Definitely not. Few attributes is a lot easier than JSX.

[1] - https://react.dev/learn/managing-state


Apple vs Oranges: A complete comparison.


Both are fruits though. Similarly these are also both front end library/framework.


I sense just a little bias in this based on the table at the beginning of the article alone.


We've been using Turbo for over a year now (similar idea to Htmx). Pretty happy not to be using React anymore. Most of the complexity is offloaded to the backend where we can handle it better with a typed language system and no middle layer (e.g. GraphQL) between our app and the database.

TypeScript is not typed fwiw, e.g. `as any`.


> TypeScript is not typed fwiw, e.g. `as any`.

So because it has a feature to disable typing it is not typed? Would you say the same thing for any language that has similar "unsafe" flags, like rust or basically all the others?


           | Static | Dynamic
    -------+--------+---------
    Strong | Rust   | (lisp?)
    Weak   | TS     | JS
TypeScript bolts on a Static type checker onto JS, and the types are erased before running in a real JS runtime. At runtime the types can change out from underneath you for many local and nonlocal reasons. This is simply impossible in Rust in general.


You'd have to try very hard to override a type in Rust when that type comes directly from your database (see SQLx).

Rust unsafe features don't allow you to override type safety, but you can directly manipulate raw pointers if you're so inclined.

TypeScript really does let you bypass the type checking entirely and drop down to just JavaScript.


Yes, typescript allows you to do that. It is entirely voluntary and you can do untypesafe things in almost any typed language.

I'm not sure how that makes typescript "not typed".


I think the parent is simply pointing out that “Typescript’s types are not sound.” That is, the types at runtime may not be what you assumed at compile-time. This happens very often at API boundaries because fetch returns `any`.

Many languages do have sound type systems, and I would argue we should prefer those.


That is certainly one interpretation, but even then within most compile-time but not runtime-checked languages you can usually perform runtime checks. For example I've used Zod (https://zod.dev/) quite a bit in typescript to do runtime typechecks at the boundaries.


I don't know much about Rust, but it has the same problem, doesn't it? As I understand it this is something the serde crate (at least) is often used to solve.

This is reasonable, given that untyped data received at a system boundary is going to need some kind of type checking no matter what the language or type system; I'm not sure that can even be fairly called a 'problem'. But it also would leave the putative line of argument rather footless, in that blaming Typescript for something Rust also requires is incoherent.

Perhaps the originator of the claim under discussion will clarify his meaning.


I think we agree, or maybe you misunderstood what I meant?


We agree, yeah. My argument is just that if you're accurately describing the argument that 'levkk is making, then 'levkk's isn't a meaningful argument: doesn't really do anything to indict one language or the other, at least if I've correctly understood serde's usage in what appears very much the identical use case.


The fetch issue (and JSON.parse/localStorage.getItem/etc) can mostly be mitigated by using type guards to “parse” data at the point of ingress.


Sure, that's true. Under the same rule, C also isn't typed because void pointer casts are possible.



What about the empty interface type in Go?[0] Is that just as problematic for you?

[0] https://go.dev/tour/methods/14


Correction: the empty interface is actually analogous to TypeScript's unknown type rather than the any type.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: