I have been building web applications in React until very recently, but now I’ve moved to HTMX. Here are the main differences so far. For reference, my React style was client-side only for web-applications.
- Client State: In React, I used Redux, which had almost all of the client state. Some state that I needed to be sticky was put into cookies. There was some transient state in useState variables in components. Redux was essentially a cache of the server database. In HTMX, the client state is in the HTML mostly in a style called Hypermedia As the Engine of Application State (HATEOAS). Right now, I do cheat a little, sending a small JavaScript object in a script tag for my d3 visualizations to use.
- Wire Protocol: To feed the React applications, I had been using a GraphQL API. In HTMX, we expect HTML from REST responses and even over the web-socket.
- DOM Updates: In React, I used a classic one-way cycle. Some event in React would trigger an API call and optimistic update to Redux. The Redux would trigger React Component re-renders. If the API failed, it would undo the Redux, and then re-render the undo and any additional notifications. In HTMX, the HTML partials sent back from the REST request or websocket use the element’s id and HTMX custom attributes to swap out parts of the DOM.
- Markup Reuse: In React, the way to reuse snippets of markup is by building components. In HTMX, you do this with whatever features your server language and web-framework provide. I am using Django, so I use tag templates or simple template inclusions. Aesthetically, I prefer JSX over the
{%%}syntax in templates, but it’s not a big deal. There are other affordances for reuse in Django/Python, but those are the two I lean on the most. - Debugging: In React, I mostly relied on browser developer tools, but it required me to mentally map markup to my source. This was mostly caused by my reliance on component frameworks, like ReactNative Paper and Material. In HTMX, the source and browser page are a very close match because I am using simpler markup and Bulma to style it. It’s trivial to debug my JavaScript now because it’s all just code I wrote.
- UI Testing: In React, I used react-testing to test the generated DOM. In Django, I am using their testing framework to test Django views and the HTML generated by templates. Neither is doing a snapshot to make sure it “looks right”. These tests are more resilient than that and are making sure the content and local state is correct. I could use Playwright for both (to test browser rendering) and it would be very similar.
Also, in general, I should also mention that the client-server architecture of my projects is also quite different. In React, I was building a fat-client, mobile-web app that I was designing so that it could work offline. The GraphQL API was purely a data-access and update layer. All application logic was in the client-side. All UI updates were done optimistically (i.e. on the client side, assuming the API update would succeed), so in an offline version, I could queue up the server calls for later.
My Django/HTMX app could never be offline. There is essentially no logic in the client side. The JavaScript I write is for custom visualizations that I am building in d3. They are generic and are fed with data from the server. Their interactions are not application specific (e.g. tooltips or filters).
This difference has more to do with what I am building, but if I needed a future where offline was possible, I would not choose HTMX (or server rendered React).