DEV Community

rugk
rugk

Posted on • Edited on

Fighting an autonomous button tag… – a story about debugging

I had this simple button tag and added some JS behavior to via a JavaScript lib that I've also written and that is already used for some other buttons on the page. Next to the button it displayed a message under some conditions and the button triggered a request that could change it. So, actually, I had expected nothing fancy here… (you can imagine, I was wrong.)

The issue

In the beginning, there was a bug: When clicking on that new button it would switch to a message that I did not expect and that should actually only be shown when I open the page.

Checking it with the debugger and diving into the code, I saw my code was executed as expected, only afterwards it did still show the strange behavior.

Actually, I noticed it triggers the code twice and thus shows the different message, but I had no idea, why it was triggered twice.
Note I had other buttons on the page and these worked fine when clicking on them. And I double-checked the HTML code, it is exactly the same… (I copied it anyway for a test, which did not help…)

Thus I added more breakpoints and went through the JS code line by line… And one thing totally confused me: the initialization code runs again. Runs twice, and triggers my button function. (which is, by itself, expected when the init code runs, because I call it manually. But only once when the page is loaded.)

However, nowhere did I call the init code again in the button click handler? Furthermore, how can it even run my init code? This code should only be run once when the page is loaded. 🤔

Going it through I finally got it: As you can see in the code, I had the button wrapped in an a tag that was not used in this case (it would only be used when I added a link), so it has to be this: It redirects the page away, because I have no href link set.

<a>
    <button id="button">CLICK ME</button>
</a>
Enter fullscreen mode Exit fullscreen mode

So I've removed that and it finally… to my surprise… it did not work. It still had the mentioned bug…

The (real) issue

So that thing needed to go on, and I already assumed a bug in the browser. After all, it should not run my init code.
I already was about to create a minified test case, at least, so I could report it somewhere. The dramatical measure was to just remove much JS, reducing things that could interfere. Given all the other buttons work, it has to be some tiny bug in my new JS, after all.

I ended up removing all my JS. This way, I could at least confirm that it is a bug in the HTML-only – if it did not appear now.

To be sure, I also manually added a "click" event listener to the button, so I could see whether the click event itself caused it:

document.getElementById("button").addEventListener("click", () => {
    console.error("test");
});
Enter fullscreen mode Exit fullscreen mode

And, yes, the small click event was triggered. But, what's more important, in this really simple test case, the bug was gone! 🎉 So at least I got the confidence the bug is in my JS code, or so I thought… (later this would turn out as absolutely wrong…)

Thinking about it a little more, I thought it must have been some kind of reload. However, I was not aware to have ever implemented this kind of thing.

Then I more or less accidentally stumbled upon an example code for a button like this one:

<button type="button">click</button>
Enter fullscreen mode Exit fullscreen mode

You see the type attribute? This blew my mind. From this point on, I know I've solved it… This time, for real. 😃

A crazy simple HTML error, discovered only after hours of debugging.

What happened?

Actually, what the button did was reloading the site. Why? Well…, because it is a “form submission button“. Look at the MDN doc for the button tag and you'll see that, by default, a button is no button, but has the type=submit.

type
The type of the button. Possible values are:

  • submit: The button submits the form data to the server. This is the default if the attribute is not specified, or if the attribute is dynamically changed to an empty or invalid value.

  • reset: The button resets all the controls to their initial values.

  • button: The button has no default behavior. It can have client-side scripts associated with the element's events, which are triggered when the events occur.

(highlighting by me)

This crazy part of the HTML5 spec requires me to explicitly define that a button is a button, because otherwise it is regarded as a submit button by default and if there is no data to submit (no input elements in the form), it just reloads the site. In my case, I even had data to submit, but it was not shown to me due to the circumstances… (see question below)

So why did not I saw it reloading?

If I had a normal web page, I would have immediately seen the reloading. However, in my case, I was developing a browser extension/add-on's options page. There is neither an address bar nor a reload indicator shown. So I could not see anything… It would have been possible to open the page in an extra tab for debugging, at least, and it retro-perspective I should have done so, but at that time I did not get that. Knowing the reload behavior would have made some things much easier as I would not have to guess which strange browser bug triggers the JS code that is supposed to only run at the page load.

Also note my page was fast and simple enough, so I could not see any visual indicators of reloading or so.

So why did it work when removing all JS?

The removal of all JS should have tested whether the bug is caused by HTML only – which, as we now know, is actually the case. However, it has pointed me in the opposite direction: the bug was not apparent anymore, letting me think the bug is in my JS code.

The reason for this was that I obviously removed all JavaScript code. Thus, the code that was triggered on reload (the init code) that showed me the visual clue of the bug (the different message) was also removed and could not trigger. 😆
So the site was still reloading, but I did not see the reloading anymore as I also removed the code that would trigger on reload.

So why did the other buttons work?

Part of why this drove me crazy is the fact that I had exactly the same buttons attached with click event handlers in exactly the same way in another part of the page.

However, as I now know, all these buttons semantically were submit buttons, as I have always missed the type="button". Despite that, they did not trigger a reload as they were not included in the form tag. Because what I did not mention yet: Obviously, the “submit“ button can only “submit“ the form if I is inside of a form tag. Without it, the browser silently ignores the semantically invalid button… arrgh…
That's why the other buttons did not trigger a reload.

Do you blame the spec?

The article description has indicated it, has not it? Actually, despite the bad circumstances and me heading directly into the wrong way, I partially do, yes. Why should I have to explicitly define that a button has no functionality? Why should I have to define that a button is a button? Why does it have a functionality by default? Especially in such a hidden way…

What project is this in/about?

It was an issue in the options page for a new feature in one of my add-ons.

Top comments (0)