`hx-target` + `hx-swap="outerHTML"` causes errors with parallel `hx-get` requests
Demo code here of the issue: https://github.com/jwendel/scratch/tree/main/htmx-multi-request
possible related issue: https://github.com/bigskysoftware/htmx/issues/2440
High-level
HTMX ([email protected]) is printing this in the Chrome console in my demo project if I switch quickly between the radio buttons:
TypeError: Cannot read properties of null (reading 'querySelector')
at htmx.js:903:46
at forEach (htmx.js:421:21)
at handleAttributes (htmx.js:898:13)
at insertNodesBefore (htmx.js:934:13)
at swapOuterHTML (htmx.js:1018:17)
at swap (htmx.js:1086:21)
at selectAndSwap (htmx.js:1150:24)
at doSwap (htmx.js:3626:25)
at handleAjaxResponse (htmx.js:3740:21)
at xhr.onload (htmx.js:3332:21)
And after this happens, the buttons that threw this error get stuck and stop sending hx-get requests to the server.
Reason
Here is the problem HTML code:
<input hx-get="/dostuff" hx-target="#updateme" hx-swap="outerHTML" type="radio" id="blue" name="drone" value="blue"/>
<label for="blue">blue</label>
<input hx-get="/dostuff" hx-target="#updateme" hx-swap="outerHTML" type="radio" id="green" name="drone" value="green"/>
<label for="green">green</label>
<div id="updateme">content here</div>
And the call to /dostuff returns:
<div id="updateme">response!</div>
Stepping through this a bunch, I believe the issue is that when you use hx-target along-side hx-get, HTMX remembers the target element (it does a query for it before sending the request), so when the XHR response comes in, it has a reference to the object (specifically the responseInfo object here: https://github.com/bigskysoftware/htmx/blob/15243b5d43e148934bbca7deb1028570afcd76ad/src/htmx.js#L3339. When the first response comes in, the div is replaced. When the second comes in, it tries to replace the remembered div, but it had been replaced in the DOM already (so it gives an error).
Steps to cause this:
- You have 2 elements that set
hx-targetto the same target div - You set
hx-swap="outerHTML"on both - Both elements issue XHR requests (
hx-getin my case) in an overlapping fashion.
Workarounds:
I think there are 2 workarounds
- Don't use
hx-swap=outerHTMLif you can get multiple requests going at the same time. I switched the setup to usehx-swap="innerHTML"and it worked just fine with multiple requests. - use
hx-sync. Specially I didhx-sync="#updateme:replace"on all the radio buttons and it resolved the issue by aborting the requests, so no request could try to update thedivthat was replaced before we get a response.
Solution idea
hx-target shouldn't remember the full object at the start of the request (do the DOM query for it again).