Remix.run Logo
johnfn 4 days ago

> When you click it, HTMX POSTs to /clicked, and whatever HTML the server returns replaces the button. No fetch(). No setState(). No npm install. No fucking webpack config.

Can someone explain something to me?

To my view, the single best idea React has is that it forces you to encapsulate the full set of states in your component - not anywhere else. For instance, if you have a "Comment" button that becomes "Submitted" after you click it, you must include that state inside the comment component. You can't hide it somewhere else (i.e., in the input field after you press cmd-enter). This is a huge thing for managing complexity - if you spread state updates to a number of different places, it becomes significantly harder to reason about how the button works.

Replacing the button with whatever the server responds with may sound simple, but it really makes it hard to answer the question of "what are all the states this button could be in". I mean, what if the server returns another button... which hits another API... etc?

The weird thing is that HTMX talks about Locality of Behavior (yay!), but doesn't seem to practice it at all?

BTW, one other thing:

> The ecosystem is why your node_modules folder is 2GB. The ecosystem is why there are 14 ways to style a component and they all have tradeoffs. The ecosystem is why "which state management library" is somehow still a debate.

> HTMX's ecosystem is: your server-side language of choice. That's it. That's the ecosystem.

Really? Python is my ecosystem. You know that people add stuff to their node_modules because they need it, right? It's not like we do it for fun. Where am I going to find a date-time picker in Python? Am I going to build it from scratch? Where is an autocomplete component in Python? Or mentions?

chamomeal 4 days ago | parent | next [-]

To your last point: If you're making an HTMX app, you're probably using built-in browser inputs instead of custom JS-based ones like react-select. If you NEED react-select, you probably should just use react. But if your site is mostly just displaying data with html and updating that data with forms, htmx will work great with much less complexity and effort.

I've only toyed around with htmx, but it really is refreshing. Of course you can't do everything that you can do with a fully-fledged client side framework. But once you try it out, you realize that more of the web is just markup + forms than you realized. I wouldn't build a live chat app with htmx, but I would totally build a reporting dashboard.

adamzwasserman 4 days ago | parent | prev [-]

The state encapsulation concern is exactly what DATAOS addresses: dataos.software

The premise is that React's "UI=f(state)" creates a synchronization problem that doesn't need to exist.

If the DOM is the authority on state (not a projection of state held elsewhere), there's nothing to sync. You read state from where it already lives.

I built multicardz on this: 1M+ cards, sub-500ms searches, 100/100 Lighthouse scores.

If you have time to read, I would very much like to hear your thoughts; genuine technical pushback welcome.

johnfn 4 days ago | parent | next [-]

I would be happy to discuss more. I am genuinely curious (though I do hold a pretty strong belief that React is a good abstraction).

When you say that the DOM is the authority on state, I’m not sure if that addresses my concern. Let’s revisit my example of the button. Image I look at the DOM and see that it’s in a “Submitted” state, and I think that’s a bug. How can I determine how it got into this buggy state? The DOM can’t answer that question, as far as I can see, because it is only an authority on the current state of the system at this exact moment. The only way I see you answering this question is by scanning every single API endpoint that could have plausibly swapped out the button for its new state - which scales at O(n) to the side of your repository! And if one API could have added HTML which then added more HTML, it seems even worse than that.

The advantage of React is that you get a crisp answer to that question at all times.

> I built multicardz on this: 1M+ cards, sub-500ms searches, 100/100 Lighthouse scores.

I believe you! But I am more concerned about readability. React makes some tradeoffs, but to me readability trumps all.

adamzwasserman 4 days ago | parent [-]

The debugging question has a direct answer: when you capture User State from the DOM (via manifest) and send it to pure functions on the backend, you get a perfect event source pattern.

Not only can I can tell you exactly who and what triggered any piece of HTML being sent to screen, I can rewind and replay it like a tape recorder.

The broader philosophical difference: I don't treat "state" as one thing. User State (what the user sees and expects to persist across refresh) lives in the DOM.

Auth state, db cursors, cache: backend concerns. Pure functions on the backend are deterministic; same input, same output, trivially testable.

I have no hate for React; I was a core member of the React Studio team. But for my use cases, this model gives me better debuggability, not worse.

lbreakjai 3 days ago | parent | prev [-]

> If the DOM is the authority on state (not a projection of state held elsewhere), there's nothing to sync. You read state from where it already lives.

But it's not, is it? The state lives in the server. If I open a page, and let it open for 2 hours, then there's no guarantee it actually represents the current state.

adamzwasserman 3 days ago | parent [-]

You are conflating all state into a big undifferentiated soup.

I reason about state differently, Here are my axioms:

1. There is no such thing as "state".` There is user/ui state, there is cache state, there is db cursor state, etc. trying to manage all of that as big undifferentiated ball is not proper separation of concerns.

2. what I call User State is the user's exceptions that they will find things "as they left them". If as a user, I do a pager refresh, I expect that my user preferences for colors and fonts will still be there. that the sorting on the table I was looking at will still still be there, that the filters I had applied to the table are still there. That is I was editing a record but had not saved, I will still be editing that record without data loss on screen but still not saved to the db (unless the app is explicitly autosave). In a word: the user state is what the user can see.

3. Ideally my User State has a single source of truth, the user can always confirm it by looking at it, and is therefore IN the front end.

4. Most, if not all, other state is stored on the server. things like the user's auth state is a -backend- concern, and should NOT be stored in the frontend.

5. class-oriented programming is extremely difficult to manage: as I like to say, every class is a petri dish for state corruption. I prefer pure functions on the backend for the reason I outline ion the dataos.software blog articles.

Pure functions are mostly deterministic (especially if you avoid floats). So you can not only count on getting the same results for the same query, you can cache them too. And you can test them trivially. Integration test? What's that?

When you capture the User State from the DOM (using a manifest so that you do not need to capture the whole DOM) and send it to the pure function on the back end, you have a perfect event source pattern. Not only can I tell you exactly who and what triggered a particular piece of html being sent o the screen, I can rewind and reply like a tape recorder.

BTW, I have no hate for React. There are some things I think it does better than any other option. I was a core member of the React Studio team (expired cert on the site but safe to visit for archeological purposes).