htmx icon indicating copy to clipboard operation
htmx copied to clipboard

Possibly buggy cache handling when using hx-select / hx-target in htmx 2.x

Open heikojansen opened this issue 1 year ago • 9 comments

I have two files "first.html"

<!doctype html>
<html>
  <head><title>First</title></head>
  <body>
    <ul hx-boost="true" hx-select="main" hx-target="main" hx-swap="outerHTML">
      <li><a href="first.html">First</a></li>
      <li><a href="second.html">Second</a></li>
    </ul>
    <main> First </main>
    <!--script src="https://unpkg.com/[email protected]"></script-->
    <script src="https://unpkg.com/[email protected]"></script>
  </body>
</html>

and "second.html"

<!doctype html>
<html>
  <head><title>Second</title></head>
  <body>
    <ul hx-boost="true" hx-select="main" hx-target="main" hx-swap="outerHTML">
      <li><a href="first.html">First</a></li>
      <li><a href="second.html">Second</a></li>
    </ul>
    <main> Second </main>
    <!--script src="https://unpkg.com/[email protected]"></script-->
    <script src="https://unpkg.com/[email protected]"></script>
  </body>
</html>

served by some (any) webserver from the same directory.

I open http://.../first.html in some browser (I tried Firefox, Chromium and MS Edge (Linux)) and perform the following steps:

Action URL in browser after action content shown after action
first.html "First"
Click on link "Second" second.html "Second"
Browser "back" first.html "First"
Click on link "Second" second.html "Second"
Browser "back" first.html "Second" <-- WTF?

After the second time I stepped back through the browser history, URL and content have diverged!

Now if I remove hx-select="main" hx-target="main" hx-swap="outerHTML" and start again (SHIFT+F5 hard reload for "first.html") everything works.

Also, if I keep hx-select="main" hx-target="main" hx-swap="outerHTML" but switch to HTMX 1.9.12 everything works, too.

So to me this really looks like a bug in HTMX 2.x?!

Can anyone confirm if it is a bug or show me what I'm doing wrong?

Thanks! Heiko

heikojansen avatar Sep 03 '24 15:09 heikojansen

yeah I was able to reproduce this and it does look like a bug to me.

checked contents of the htmx-history-cache in local storage and I get this which has second in both history data items so it seems the bug is in the save current page to history feature somehow

[
    {
        "content": "\n    <ul hx-boost=\"true\" hx-select=\"main\" hx-target=\"main\" hx-swap=\"outerHTML\">\n      <li><a href=\"first.html\">First</a></li>\n      <li><a href=\"second.html\" class=\"\">Second</a></li>\n    </ul>\n    <main class=\"\"> Second </main>\n    <!--script src=\"https://unpkg.com/[email protected]\"></script-->\n    <script src=\"https://unpkg.com/[email protected]/dist/htmx.js\"></script>\n  \n",
        "scroll": 0,
        "title": "Second",
        "url": "/second.html"
    },
    {
        "content": "\n    <ul hx-boost=\"true\" hx-select=\"main\" hx-target=\"main\" hx-swap=\"outerHTML\">\n      <li><a href=\"first.html\">First</a></li>\n      <li><a href=\"second.html\" class=\"\">Second</a></li>\n    </ul>\n    <main class=\"\"> Second </main>\n    <!--script src=\"https://unpkg.com/[email protected]\"></script-->\n    <script src=\"https://unpkg.com/[email protected]/dist/htmx.js\"></script>\n  \n",
        "scroll": 0,
        "title": "Second",
        "url": "/first.html"
    }
]

MichaelWest22 avatar Sep 03 '24 22:09 MichaelWest22

Ok found your issue. similar issues were reported in #1076

The cause of your issue is that you have placed the htmx running javascript code inside the body of your page that is getting replaced with every boosted navigation and history restore. This is causing the internal global variable currentPathForHistory to get into a confused state with the wrong value somehow. Moving the htmx script to the page head and then not replacing it every time resolves the issue.

MichaelWest22 avatar Sep 03 '24 23:09 MichaelWest22

Wow, that was quick.

The cause of your issue is that you have placed the htmx running javascript code inside the body of your page that is getting replaced with every boosted navigation and history restore.

Hm, but the problem occurred while using hx-select="main" hx-target="main" - and that the <script>-Tag is outside of main so shouldn't get replaced?

Anyway, I'll give this a try in the simplified example code and also in the real application and will report back if the problem is solved there, too.

heikojansen avatar Sep 04 '24 06:09 heikojansen

Could hx-history-elt be an alternative to putting the script in head maybe? As by default, the body element is the one used and restored through history navigation

Telroshan avatar Sep 04 '24 07:09 Telroshan

Being fairly new to HTMX I had so far overlooked hx-history-elt but I'll give that a try, too. Thanks for the hint!

heikojansen avatar Sep 04 '24 07:09 heikojansen

Yeah sorry it is not the boosted swapping that seems to be swapping out the body as you are only targeting main as you said. But history is replacing body when you go back which seems to be launching a new copy of htmx js script. The dev tools even starts showing the htmx.js file running with a VM1234 style prefix which I think means the htmx javascript is executing in a old disconnected and separated vm instance now. The state between the two instances of htmx.js seem different and it causing the problems for the currentPathForHistory variable which is how htmx tracks the current url. Anyway moving the script to recommended places like the head seems the best solution.

MichaelWest22 avatar Sep 04 '24 12:09 MichaelWest22

Also did a quick test locally with replacing the global variable currentPathForHistory

  currentPathForHistory = path

with

localStorage.setItem('htmx-current-path-for-history', path)

to move it to localstorage plus some checks for when localstorage is disabled Confirmed this works well and resolves the issue but not sure if this is worth making into a PR.

Edit: actually this should be in sessionStorage so multiple tabs work as expected.

MichaelWest22 avatar Sep 04 '24 23:09 MichaelWest22

OK, I can confirm that everything works fine if the <script> element is added to the <head> instead of the <body>.

However, since I could not reproduce the problem when using HTMX 1.9.12 it looks like a regression which still might warrant a fix in 2.x.

Otherwise I would suggest to at least add a more prominent warning to the docs not to load HTMX from a <script> element within the <body>. Placing those script elements just before the closing <body> tag is something at least I am quite used to.

Unfortunately I did not have time yet for experimenting with hx-history-elt so cannot comment on the effects it might have here.

Heiko

heikojansen avatar Sep 13 '24 10:09 heikojansen

Yeah current docs (and v1) for installing has:

Installing
Htmx is a dependency-free, browser-oriented javascript library. This means that using it is as simple as adding a <script> tag to your document head. There is no need for a build system to use it.

Via A CDN (e.g. unpkg.com)
The fastest way to get going with htmx is to load it via a CDN. You can simply add this to your head tag and get going:

But that is about all it has on the subject. Not sure what is the best way to add to this.

I think v1 had different history implementation and they improved a few things which has lead to this change. Even in v1 it would have had the same issue reloading and running the whole htmx script state with every history/boost which could cause issues if you override the htmx config items values in my testing as these can all get reset.

MichaelWest22 avatar Sep 13 '24 13:09 MichaelWest22