Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

swapError when removing elements on htmx:beforeCleanupElement #3046

Open
eros404 opened this issue Nov 28, 2024 · 2 comments
Open

swapError when removing elements on htmx:beforeCleanupElement #3046

eros404 opened this issue Nov 28, 2024 · 2 comments

Comments

@eros404
Copy link

eros404 commented Nov 28, 2024

Htmx version: 2.0.3

I use Htmx with a ui library (Kendo).
I listen to event htmx:beforeCleanupElement for kendo to clean nodes generated by the library.

An exception occurred in the function swapInnerHTML (line 1739):
Failed to execute 'removeChild' on 'Node': parameter 1 is not of type 'Node'.

function swapInnerHTML(e, t, n) {
    const r = e.firstChild;
    if (insertNodesBefore(e, r, t, n),
    r) {
        for (; r.nextSibling; )
            cleanUpElement(r.nextSibling), // this is where event htmx:beforeCleanupElement is raised
            e.removeChild(r.nextSibling); // Exception here
        cleanUpElement(r),
        e.removeChild(r)
    }
}

It seems that when I clean up on event, I destroy the nextSibling and the exception is raised.


Edit with the code found in the repo rather than the one found in my debugger:

function swapInnerHTML(target, fragment, settleInfo) {
    const firstChild = target.firstChild
    insertNodesBefore(target, firstChild, fragment, settleInfo)
    if (firstChild) {
      while (firstChild.nextSibling) {
        cleanUpElement(firstChild.nextSibling)  // this is where event htmx:beforeCleanupElement is raised
        target.removeChild(firstChild.nextSibling) // Exception here
      }
      cleanUpElement(firstChild)
      target.removeChild(firstChild)
    }
  }
@MichaelWest22
Copy link
Contributor

I think if you use the htmx:beforeCleanupElement event to remove the the element referenced in the event then it would cause this issue. This breaks it as it expects the element to be remain so it can remove it itself. It would be nice to inside the cleanUpElement() function to not call htmx:beforeCleanupElement or deInitNode() on the element if it does not contain htmx internalData property as this would solve your issue but I can't see this happening as many users like yourself may want to be notified of all elements and not just htmx attached ones to run their custom de-init code on before they are removed.

It may be possible to try adding

    if (bodyContains(r.nextSibling)) { e.removeChild(r.nextSibling) }

to check if it exists before removing it.

From the documentation for this event it says

This event is triggered before htmx [disables] an element or removes it from the DOM.

which implies htmx is is likely to be removing this item from the DOM so ideally you should let it do its thing because by removing it early yourself it could prevent htmx from cleaning up its impacts as well. So ideally you should use this event to help de-init any listeners or other effects etc on the element you may have scripted in but leave the node itself for any further cleanup.

@eros404
Copy link
Author

eros404 commented Nov 29, 2024

I made further investigations.

The exception is raised when, listening to the event htmx:beforeCleanupElement, I delete the concerned node and this one is the last of the container being swapped.

This is because the property nextSibling change depending on the context, if I delete the next sibling then the next time the property is called it will be the other next.
In my case, I delete the last sibling, so it throws when trying to remove something that is in fact null.

It also means that as of today when I delete the node concerned by the event, the next one will be deleted without receiving the event htmx:beforeCleanupElement.

So, I guess the fix is more something like :

function swapInnerHTML(target, fragment, settleInfo) {
    const firstChild = target.firstChild
    insertNodesBefore(target, firstChild, fragment, settleInfo)
    if (firstChild) {
      while (firstChild.nextSibling) {
        const tmpSibling = firstChild.nextSibling;
        cleanUpElement(tmpSibling)
        if (target.contains(tmpSibling))
          target.removeChild(tmpSibling)
      }
      cleanUpElement(firstChild)
      target.removeChild(firstChild)
    }
  }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants