Bug Report: Unclosable Menu

November 15, 2020

This issue happened while rewriting this site as a Svelte app. I was working on the Navigation component, trying to factor out a Menu component, and wanted to keep the menu state in a store to share between them. (I later ended up implementing this as a page prop.) I made the changes, but on testing, I noticed I could open the menu, but not close it. Strange. Well, I could close it, but only by clicking away from the menu, which is some functionality I had implemented a few weeks ago…

Since the store was the last thing I changed, I started my debugging by undoing that change, and was surprised that the problem still existed. I tried logging every state change, but my state management was working correctly. My component saw the menu as closed and toggled it open, even though that was the opposite what I, the user, was seeing. The next debugging idea I had was to log the value from the react loop using $: console.log(), and very surprisingly, the value was being toggled correctly. What ultimately gave me the final clue I needed to solve the puzzle, was I removed the svelte action I had added to the menu to close on clicking away. The button worked again! I had added the functionality in a hurry and apparently hadn’t tested the change fully.

So now I know what caused it, but why? The way the action works is it registers a new click event listener on the whole document. This click handler checks if the clicked target is not the menu HTMLElement, and if not, it calls closeMenu to hide it. The button element is, in fact, outside of the menu. The button handler is a simple toggle of isMenuOpen. When I click to open, everything is fine, but when I try to close it with the button, what happens is the click event gets triggered, the event target is outside, and the menu is closed. Later, the svelte update loop runs, and sees the menu is closed Since the action is a toggle, the menu is (re-)opened This explains another behavior I saw where if I rapidly clicked the button, the menu would flicker a bit. It was actually closing, but then a few milliseconds later the react loop ran and reopened it.

The fix I implemented is to consider the button “inside”. I gave the menu button element an id so we could check for it. The event handler function gets the event as a parameter. When checking initially, the click target was the svg instead of the button. I added the pointer-events: none style to the svg, which makes it click-transparent, so now the target is the button. I then check for the menuButton id and don’t call closeMenu if the user clicked the button.