Handling form errors in htmx
21 points by yawaramin
21 points by yawaramin
I think this is a pretty neat generalized solution for anyone comfortable using the browser’s validation UI.
With that said, it’s not a very “htmx way”, as it uses JSON response payloads. To do validation a more “htmx way”, I recommend using the response targets extension instead, and using appropriate HTTP status codes to indicate different classes of errors.
You can use either HTML5 validation or client-side javascript validation where it makes sense. But any form fields requiring a server round trip (e.g. username availability) can simply use a debounced data-hx-trigger
(e.g. data-hx-trigger="keyup changed delay:300ms"
coupled with data-hx-target-[4,5]*="#username_errors"
targeting the HTML element where errors for that field should be rendered.
This feels like a more natural htmx solution to me.
it’s not a very “htmx way”
Well, htmx was purposely designed from the beginning to do swaps only on ‘success’ responses by default, meaning it deliberately leaves the interpretation and handling of an error response to the developer. It’s also part of the htmx philosophy to reuse the capabilities of the web platform as much as possible, instead of rolling our own. And the Constraint Validation API is part of the web platform to be sure.
Thus, I humbly submit that it is very much in line with the htmx philosophy overall.
I don’t mean to disparage anything by saying it’s not the htmx way. But the title has htmx in it, so it is worth noting that the solution probably wouldn’t be considered idiomatic.
To put it more precisely, htmx is for building hypermedia applications, and JSON is not a hypermedia.
But nobody is obligated to build applications 100% the htmx way or 100% with hypermedia. It’s a nice solution; just not a hypermedia solution.
That’s true, but I hope it’s clear that by deciding to use the browser’s native Contraint Validation API, we effectively don’t have any leeway in the decision. The error response has to be JSON because the CVAPI is JavaScript-only. Sure, we could use ‘hypermedia’ only ie send back HTML, but then we are saying we’re not going to use the CVAPI–which goes against the goal–or we are saying we’re going to parse the HTML to extract the error messages–which defeats the purpose.
Everything has its place–htmx and hypermedia are used for good reasons. But there are also good reasons and use cases when something else is a better choice even inside an htmx app. Purity is not the end goal here–pragmatism is allowed.
I think form validation should be done on the client side with JS first then on the server to ensure it’s correct. If you do it that way, the sad path will only be triggered by bots and other bad actors, so it can be a very minimal “Bad Request” message with no further details.
Just to be clear, I am all for using the HTML built-in validations, like required
and so on, which don’t require writing any JS. As to writing custom JS for validations, my thought is that the backend is already doing it–backend frameworks usually have fairly sophisticated form validation features. And the htmx philosophy is minimal, targeted JS and avoiding duplication of effort.
I would expect that many forms would require at least some server-side validation, like checking if an account name is available (basically, any validation they depends on the database).
Are you saying that for this cases we should not rely on form’s POST for validation UX, and instead should introduce some dedicated server-side API like /is-name-available
that the JS should poke before POSTing the form?
Nah, those are part of the application domain so you do need to handle them nicely. It’s just stuff like malformed inputs that stop needing nice behaviour once client side validation eliminates them.
My question is how exactly I am going to handle them nicely? Am I POSTing the form and parse JSON with errors after submission? How that works if I want to say “username available” while the user types? Do I POST the from on a timer with a special flag that says “not really a POST”? Do I have a separate API entirely? If I have a separate API, does it mean I have to serialization paths for forms, from-encoded and JSON?
This are somewhat naive questions, as this is well outside of my area of expertise, but it bugs me that I don’t “see” the obvious pattern here.
Username reservation is a special case. What I see a lot of lately is a thing where there’s a green check if the username is available, but that obviously is racey, so maybe there should be a thing where the AJAX call to see if janedoe
is available locks it for a certain amount of time.
I had to do this recently and ended up using a similar solution (at least, the part adding an event listener and checking the status code). The part I don’t like is that this solution assumes there’s only one type of HTMX request on the entire page. We could check evt.detail.target
or evt.detail.elt
to differentiate them, but we now have two sections of request logic between the htmx attributes and the JS waiting for response errors.
The response targets extension looks like a good solution to this, as others mentioned.
We are differentiating by the HTTP status code. If it’s 422 then we know it’s a form validation error. If it’s something else then we handle it another way.