htmx
htmx copied to clipboard
Static template that doesn't parse the response
With a current use case, the response from the POST isn't important - the side effect is handled by path-deps and that handles the re-render. It is only important to let the user know there if was an error.
As such, it would be helpful to be able to provide a static template block that does not parse the response.
For example:
<div id="hidden" style="display:none"></div>
<div id="unrecoverable-error"></div>
<template id="unrecoverable-error-template">Some static message explaining that an unknown error was encountered.</template>
<form
hx-patch="/endpoint"
hx-target="#hidden"
hx-target-error="#unhandled-error"
handlebars-template="unhandled-error-template"
hx-on-htmx-before-request="$('#unhandled-error').empty()"
>
<input type="hidden" name="some-trigger-key" value="1">
<button type="submit">Submit</button>
</form>
Using the available template engines causes a Parsing error in Javascript when JSON isn't returned by a 500 error and htmx falls back to inserting the response into "#unhandled-error" instead of the static message.
Being able to force the static message to display would be very beneficial.
As you linked the v1 docs, I suppose you're using htmx v1 and not v2 here ?
Looking at the code, I suppose you're using the response targets extension too along client-side-templates?
Is the mismatch between ids relevant (unrecoverable vs. unhandled)?
<template id="unrecoverable-error-template"> vs. handlebars-template="unhandled-error-template"
and
<div id="unrecoverable-error"></div> vs hx-target-error="#unhandled-error" and $('#unhandled-error').empty()
Is the mismatch between ids relevant (unrecoverable vs. unhandled)?
<template id="unrecoverable-error-template">vs.handlebars-template="unhandled-error-template"and<div id="unrecoverable-error"></div>vshx-target-error="#unhandled-error"and$('#unhandled-error').empty()
No - sorry that is a copy paste error from reformatting the real markup to be easier to read for the example.
What happens here is the server returns a response with an error status code. HTMX produces a Json Decoding error because the error response isn't formatted with JSON. And then HTMX inserts the server response instead of the static message from the template.
htmx:beforeSwap has currently undocumented isError and serverResponse properties.
Perhaps you could add something like:
hx-on-htmx-before-swap="if (event.detail.isError) event.detail.serverResponse = '{}'"
htmx:beforeSwaphas currently undocumentedisErrorandserverResponseproperties.Perhaps you could add something like:
hx-on-htmx-before-swap="if (event.detail.isError) event.detail.serverResponse = '{}'"
Awesome! Thanks!
htmx:beforeSwaphas currently undocumentedisErrorandserverResponseproperties.Perhaps you could add something like:
hx-on-htmx-before-swap="if (event.detail.isError) event.detail.serverResponse = '{}'"
I tried this:
<form
hx-patch="/placeholder/url"
hx-target="#devnull"
hx-target-error="#unhandled-error"
handlebars-template="unhandled-error-template"
hx-on-htmx-before-request="$('#unhandled-error').empty()"
hx-on-htmx-before-swap="if (event.detail.isError) event.detail.serverResponse = '{}'"
>
It did not solve the issue. Note that it seems to work without hx-on-htmx-before-swap="if (event.detail.isError) event.detail.serverResponse = '{}'" for 4XX errors. I am observing this issue with a 500 error. Is error 500 significant here?
According to docs, default handling is:
responseHandling: [
{code:"204", swap: false}, // 204 - No Content by default does nothing, but is not an error
{code:"[23]..", swap: true}, // 200 & 300 responses are non-errors and are swapped
{code:"[45]..", swap: false, error:true}, // 400 & 500 responses are not swapped and are errors
{code:"...", swap: false} // catch all for any other response code
]
So there should be no difference between 4xx and 5xx errors.
The response-targets extension can change the isError value, so possibly it's always setting it to false:
isErrorflag on thedetailmember of an event associated with swapping the content withhx-target-[CODE]will be set tofalsewhen error response code is received. This is different from the default behavior. You may change this by setting a configuration flaghtmx.config.responseTargetUnsetsErrortofalse(default istrue).
Possibly also need to set event.detail.shouldSwap to true, although this may already be handled by the response-targets extension.
You can get value event.detail.xhr.status for response code number.
Here is how htmx matches status code:
function codeMatches(responseHandlingConfig, status) {
var regExp = new RegExp(responseHandlingConfig.code)
return regExp.test(status.toString(10))
}
Note that it seems to work without hx-on-htmx-before-swap="if (event.detail.isError) event.detail.serverResponse = '{}'" for 4XX errors. I am observing this issue with a 500 error. Is error 500 significant here?
As you said earlier @thenewguy ,
HTMX produces a Json Decoding error because the error response isn't formatted with JSON
So the issue is not the error code, but rather that the response isn't JSON.
The client-side-templates indeed expects the response to be JSON, from what I understand, as contextual data to pass to the templating engine (I never used this extension, so I hope I get this right)
https://github.com/bigskysoftware/htmx/blob/30ab5c12b1cbdf51bb4c3f36b1ffbf663da9f4a0/dist/ext/client-side-templates.js#L28-L30 https://github.com/bigskysoftware/htmx/blob/30ab5c12b1cbdf51bb4c3f36b1ffbf663da9f4a0/dist/ext/client-side-templates.js#L35
If your server properly answers with a JSON on a 4xx error, then it works as expected, if it doesn't answer with JSON (error 5xx in your case) then it can't. If you were to simply add a try catch here and avoid throwing an error, it would likely not solve your usecase as the extension would simply return the unmodified text, that htmx would then insert instead of the static template, giving the same result that you're seeing, simply without the error in the console. As so, I think it's a server-response issue that we can't expect the extension to manage on its own, as it's doing what is advertised.
What to do
I think you have to make it a proper JSON in any case, be it by making sure your server returns a JSON even on a 5xx error, or by adding some custom client-side code to replace the server response if it is not valid JSON.
By default, if (event.detail.isError) will be true for any error code, though what you want to check here instead is likely whether the response text is valid JSON or not (if it is valid JSON, you likely want to pass that JSON to the templating engine rather than an empty one, which you likely only want as a fallback).
You can probably handle this with a try catch trying to JSON.parse the answer ; if you end up in the catch block, the answer is not valid JSON, then you replace it with an empty object {}.
Note that the response-targets extension can reset isError depending on htmx.config.responseTargetUnsetsError that it introduces, see its documentation about that ; as @scrhartley said, you can then rely on event.detail.xhr.status to check if the code is >= 400 yourself.
Hope this helps!
PS: as you're using htmx 1, make sure to use the V1 docs which doesn't include responseHandling that is a htmx 2 feature
PS2: if you can and don't need IE11 support, I strongly encourage upgrading to htmx2
Note that it seems to work without hx-on-htmx-before-swap="if (event.detail.isError) event.detail.serverResponse = '{}'" for 4XX errors. I am observing this issue with a 500 error. Is error 500 significant here?
As you said earlier @thenewguy ,
HTMX produces a Json Decoding error because the error response isn't formatted with JSON
So the issue is not the error code, but rather that the response isn't JSON. The
client-side-templatesindeed expects the response to be JSON, from what I understand, as contextual data to pass to the templating engine (I never used this extension, so I hope I get this right)https://github.com/bigskysoftware/htmx/blob/30ab5c12b1cbdf51bb4c3f36b1ffbf663da9f4a0/dist/ext/client-side-templates.js#L28-L30
https://github.com/bigskysoftware/htmx/blob/30ab5c12b1cbdf51bb4c3f36b1ffbf663da9f4a0/dist/ext/client-side-templates.js#L35
If your server properly answers with a JSON on a 4xx error, then it works as expected, if it doesn't answer with JSON (error 5xx in your case) then it can't. If you were to simply add a try catch here and avoid throwing an error, it would likely not solve your usecase as the extension would simply return the unmodified text, that htmx would then insert instead of the static template, giving the same result that you're seeing, simply without the error in the console. As so, I think it's a server-response issue that we can't expect the extension to manage on its own, as it's doing what is advertised.
What to do
I think you have to make it a proper JSON in any case, be it by making sure your server returns a JSON even on a 5xx error, or by adding some custom client-side code to replace the server response if it is not valid JSON. By default,
if (event.detail.isError)will betruefor any error code, though what you want to check here instead is likely whether the response text is valid JSON or not (if it is valid JSON, you likely want to pass that JSON to the templating engine rather than an empty one, which you likely only want as a fallback). You can probably handle this with a try catch trying toJSON.parsethe answer ; if you end up in thecatchblock, the answer is not valid JSON, then you replace it with an empty object{}. Note that theresponse-targetsextension can resetisErrordepending onhtmx.config.responseTargetUnsetsErrorthat it introduces, see its documentation about that ; as @scrhartley said, you can then rely onevent.detail.xhr.statusto check if the code is>= 400yourself.Hope this helps!
PS: as you're using htmx 1, make sure to use the V1 docs which doesn't include
responseHandlingthat is a htmx 2 feature PS2: if you can and don't need IE11 support, I strongly encourage upgrading to htmx2
I appreciate your attention to this matter. I understand that returning JSON from the server would prevent this error from occuring but unfortunately that is not a viable solution in this case.
I had hoped that I was overlooking a simple workaround. Since it appears that this cannot be easily handled in client code, please consider adding a new template type option that makes it possible to insert static html templates in situations such as this.
Thanks!
Since it appears that this cannot be easily handled in client code
Ofc depends on what you consider "easy" or not @thenewguy , but I feel the following would do the trick:
document.body.addEventListener("htmx:beforeSwap", function (event) {
const isError = event.detail.xhr.status >= 400
const isHandlebarTemplate = htmx.closest(event.detail.elt, "[handlebars-template], [handlebars-array-template]")
if (isError && isHandlebarTemplate) {
try {
JSON.parse(event.detail.serverResponse)
} catch {
event.detail.serverResponse = "{}"
}
}
})
Disclaimer: I didn't test this at all, but that's what I had in mind when typing the previous message The ideas here are:
- listening on the body as a general strategy to fix the JSON whenever needed, without having to care about where we are in the DOM
- checking error responses only (depending on your usecase, you might even want to do it for all statuses if the response to be swapped in a handlebars template isn't JSON)
- checking if the target element is a client side template (note that I'm simply running the same
closestcheck that the extension does - if the response cannot be parsed to JSON, replace that response by an empty JSON object
Hope this helps!
please consider adding a new template type option that makes it possible to insert static html templates in situations such as this
Sounds reasonable to me! If you are interested btw, feel free to investigate & open a PR for that!