htmx icon indicating copy to clipboard operation
htmx copied to clipboard

`hx-target` + `hx-swap="outerHTML"` causes errors with parallel `hx-get` requests

Open jwendel opened this issue 1 year ago • 0 comments

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:

  1. You have 2 elements that set hx-target to the same target div
  2. You set hx-swap="outerHTML" on both
  3. Both elements issue XHR requests (hx-get in my case) in an overlapping fashion.

Workarounds:

I think there are 2 workarounds

  1. Don't use hx-swap=outerHTML if you can get multiple requests going at the same time. I switched the setup to use hx-swap="innerHTML" and it worked just fine with multiple requests.
  2. use hx-sync. Specially I did hx-sync="#updateme:replace" on all the radio buttons and it resolved the issue by aborting the requests, so no request could try to update the div that 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).

jwendel avatar Jun 07 '24 01:06 jwendel