bigskysoftware / htmx

</> htmx - high power tools for HTML
https://htmx.org
Other
38.67k stars 1.32k forks source link

Response with leading or trailing newline characters causes "Cannot read properties of null (reading 'htmx-internal-data')" error #2942

Closed gritvald closed 1 month ago

gritvald commented 1 month ago

htmx version: 2.0.2

I have the following input:

<input
    type="text"
    name="phrase"
    hx-get="/search"
    hx-trigger="input changed delay:300ms"
    hx-target="#availableAssetList"
/>

The /search endpoint returns HTML with some newlines at the top and bottom of the response (line numbers are not part of the response):

1. 
2. 
3. 
4.     <ul >
5.         
6.     </ul>
7. 
8. 

An exception is thrown from the getInternalData function because the elt param has a null value:

function getInternalData(elt) {
  const dataProp = 'htmx-internal-data'
  let data = elt[dataProp]  // elt === null
  if (!data) {
    data = elt[dataProp] = {}
  }
  return data
}

Below is the value of the child variable from the makeAjaxLoadTask function call frame:

child = {
    assignedSlot: null,
    baseURI: "...",
    childNodes: NodeList [],
    data: "\n\n\n    ",
    firstChild: null,
    isConnected: true,
    lastChild: null,
    length: 7,
    nextElementSibling: ul.htmx-added,
    nextSibling: ul.htmx-added,
    nodeName: "#text",
    nodeType: 3,
    nodeValue: "\n\n\n    ",
    ownerDocument: document,
    parentElement: div#availableAssetList.htmx-settling,
    parentNode: div#availableAssetList.htmx-settling,
    previousElementSibling: null,
    previousSibling: null,
    textContent: "\n\n\n    ",
    wholeText: "\n\n\n    "
}

Next, child is passed to asElement, which returns null, which is then passed through the processNode -> initNode -> getInternalData call chain:

function makeAjaxLoadTask(child) {
  return function() {
    removeClassFromElement(child, htmx.config.addedClass)
    processNode(asElement(child))
    processFocus(asParentNode(child))
    triggerEvent(child, 'htmx:load')
  }
}

When I strip leading and trailing newlines from the response everything works fine.

Maybe it's somehow related to #2613 and #2913.

gritvald commented 1 month ago

I've found the issue.

I had defined a custom Node class that shadowed the built-in Node. This was the reason why insertNodesBefore was scheduling a settling task for text nodes - TEXT_NODE was undefined in my custom Node class, which is why the following test was always passing:

if (child.nodeType !== Node.TEXT_NODE && child.nodeType !== Node.COMMENT_NODE) {
    settleInfo.tasks.push(makeAjaxLoadTask(child))
}