Search code examples
javascriptmdc-components

MDC Menu - select event listener working randomly


I have a MDC Menu component, where I would like to setup a click or select event listener for list items. I have tried with various events like 'click', 'select', 'selectionchange', 'selectstart', but none of them worked. In documentation it says that MDCMenu:selected

Used to indicate when an element has been selected. This event also includes the item selected and the list index of that item.

I have tried to listen to that event as well, but nothing has worked:

menu.listen("MDCMenu:selected", e => console.log(e));

You can see the sandbox here. How should the event listener be setup for MDC menu component?

Update

Since after I was informed in the comments that for other users the code is actually working. I went to test it myself with other browsers, and in safari and firefox it was working fine on my mac machine, but in Chrome I still have a problem. I have the chrome version 83.0.4103.61. I have updated the codesandbox with suggestions from the comments, but I can now see that if press few times select options, that it starts to work very randomly all of a sudden in all of the browsers.


Solution

  • It appears that the inconsistency is due to a race condition. Clicking on the menu causes the focus to leave the input which causes the menu to close. And the menu closing causes focus to move back to the input making it open again.

    The issue is that the menu often closes before the menu has a chance to send out the selected event.

    You need to either prevent the menu from closing on focusout or set a generous timeout before closing the menu.

    input.listen("focusout", () => {
      setTimeout(() => {
        menu.open = false;
        // The timer might need to be fiddled with. Needs to not be too big or too small.
      }, 120);
    });
    

    https://codesandbox.io/s/new-brook-o7k1j?file=/src/index.js


    Here's another option without the timing issues of setTimeout. It uses timeouts of 0 to mimic setInterval in order to re-order the timing of events instead by pushing them to the end of the event queue. This should be safer and less prone to the race condition issue from before.

    let menuFocused = false;
    input.listen("focusin", () => {
      if (!menuFocused) menu.open = true;
    });
    input.listen("click", () => {
      menu.open = true;
    });
    
    menu.listen("focusin", () => {
      menuFocused = true;
    });
    menu.listen("focusout", () => {
      // This interval is to help make sure that input.focusIn doesn't re-open the menu
      setTimeout(() => {
        menuFocused = false;
      }, 0);
    });
    input.listen("focusout", () => {
      setTimeout(() => {
        if (!menuFocused) menu.open = false;
      }, 0);
    });
    

    https://codesandbox.io/s/proud-hooks-c9qi5?file=/src/index.js