How I fixed htmx
redeclaration of let element
with hx-boost=true
Table of contents
htmx
is gaining popularity in django
ecosystem. For example, I have created my latest project with django
+ htmx
. The sites user page was previously written using svelte
but I was not using django
to it's full capability. ( django templating language
is such a blessing | I dont even have to deal with tokens and refresh and all other nonsenses )
Thats when I started rewriting user
from svelte
to django
+ django_cripsy_forms
The problem¶
When I was writing the user code in django I planned to do realtime lookup of username availablity. So thats what I did.
Heres the django code :
<!--signup.html-->
<!--Bunch-of-code-rendered-by-django-crispy-forms-->
<!--This is the problematic code block-->
<script>
let el = document.querySelector('#id_username');
el.addEventListener('input', async () => {
// Do a bunch of works here
});
</script>
Soon enough I was running into this error ( with hx-boost=true
in the div after body tag ) whenever I revisited the page.
Uncaught SyntaxError: redeclaration of let el
<anonymous> http://127.0.0.1:8000/user/login/ line 1 > injectedScript:1
ct http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
ht http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
Y http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
ht http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
ue http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
o http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
Y http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
o http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
setTimeout handler*o http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
fr http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
onload http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
lr http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
Ue http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
I http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
ze http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
Y http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
ze http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
Ue http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
Ue http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
pt http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
mt http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
Y http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
mt http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
ue http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
o http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
Y http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
o http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
setTimeout handler*o http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
fr http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
onload http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
lr http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
Ue http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
I http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
ze http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
Y http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
ze http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
Ue http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
Ue http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
pt http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
mt http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
Y http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
mt http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
ue http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
o http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
Y http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
o http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
setTimeout handler*o http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
fr http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
onload http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
lr http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
Ue http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
I http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
ze http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
Y http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
ze http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
Ue http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
Ue http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
pt http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
mt http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
Y http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
mt http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
<anonymous> http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
pr http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
<anonymous> http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
<anonymous> http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
<anonymous> http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
<anonymous> http://127.0.0.1:8000/static/js/htmx/htmx.min.js:1
line 1 > injectedScript:1:1
This was something I never ran into before. As HTMX
was quite new I didn't find much resource on this weird error. I asked on their discord channel about this particular error ( this wasn't that uncommon as another user reported this error on their discord thread ). But I didn't find any response. Do I started digging deeper into this mess.
At a glance nothing seemed wrong ( I wrote couple project in react
, svelte
| So I had a good understanding of JS ).. Maybe because I was not familiar with django
and html
I thought. I have never stumbled upon this error in my life before.
Digging deeper¶
So after a while I thought of removing htmx
and just doing things the plain django
way. Things started to work nicely again. So was it related to htmx
afterall? htmx
for those of you dont know does things in a particular way with hx-boost
.
- It fetches the anchor tags
html
. - Replaces
body
with saidhtml
. - Re executes
scripts
inside thosebody
tags.
So naturally I thought of disabling execution of script
tags by moving the scripts to the head
. It worked :D.
But according to htmx author. After 2.0
release htmx
would re execute the scripts on the head. At the time of writing there is head-support
extension that enables said functionality and it was to be merged into htmx by 2.0
release.
The Solution¶
So I was out of hope at this point. Then I stumbled upon this stackoverflow thread ( god bless stackoverflow ). Here I learned about scoping functions. I was familiar with function scoping back in my react
days as react
didn't support top level await codes and I had to do something like this :
(async () => {
await fetch('');
})();
So I modified my code like this :
<script>
(() => {
let el = document.querySelector('#id_username');
el.addEventListener('input', async () => {
// Do a bunch of works here
});
})();
</script>
and the error was fixed. ( MAGICALLY!! )
But wait there's no magic actually¶
This type of scoping function doesn't work in all cases. For example, if you go to search page on this blog you can see that it's not enhanced. What gives ?
It seems that whenever i was nagivating to that link while using flamethrower-router
the webpack
was broken for some reason ( maybe i will move away from flamethrower-router
at some point ).
So your milage may vary on this front ( should all of your hyperlinks
work depends greatly on the libraries you use on your website )
For me the hx-boost
worked with my stack :
- Django
- Tailwind
- HTMX
- Alpine
- jQuery
Moving forward¶
If you see the pages source. You can see that the site is not written in some fancy JavaScript
framework but it feels like an SPA ( Single Page Application ). Thats because the site is using a small JavaScript
library called flamethower-router
( for those of you who follow fireship.io
you guys should know what this is ).
But I ran into the exact same error that I faced with htmx
( maybe its the issue with these frontend routers ). When i ran the site without scoping the script
tag i ran into :
Uncaught SyntaxError: redeclaration of let themeToggleDarkIcon
<anonymous> how-i-fixed-htmx-redeclaration-of-let-element-with-hx-boosttrue.html line 7 > injectedScript:1
s main.js:92
n main.js:86
reconstructDOM main.js:197
go main.js:104
onclick (index):1
how-i-fixed-htmx-redeclaration-of-let-element-with-hx-boosttrue.html line 7 > injectedScript:1:1
So I modified the theme's original script to be scoped.
(() => {
let themeToggleDarkIcon = document.getElementById('theme-toggle-dark-icon');
let themeToggleLightIcon = document.getElementById(
'theme-toggle-light-icon'
);
if (
localStorage.getItem('color-theme') === 'dark' ||
(!('color-theme' in localStorage) &&
window.matchMedia('(prefers-color-scheme: dark)').matches)
) {
themeToggleLightIcon.classList.remove('hidden');
} else {
themeToggleDarkIcon.classList.remove('hidden');
}
let themeToggleBtn = document.getElementById('theme-toggle');
themeToggleBtn.addEventListener('click', function () {
themeToggleDarkIcon.classList.toggle('hidden');
themeToggleLightIcon.classList.toggle('hidden');
if (localStorage.getItem('color-theme')) {
if (localStorage.getItem('color-theme') === 'light') {
document.documentElement.classList.add('dark');
localStorage.setItem('color-theme', 'dark');
} else {
document.documentElement.classList.remove('dark');
localStorage.setItem('color-theme', 'light');
}
} else {
if (document.documentElement.classList.contains('dark')) {
document.documentElement.classList.remove('dark');
localStorage.setItem('color-theme', 'light');
} else {
document.documentElement.classList.add('dark');
localStorage.setItem('color-theme', 'dark');
}
}
});
})();
Moving forward I hope to see more sites powered using a frontend SPA
router and finally put an end to the pesky page refresh.
— Baseplate-admin