title: My custom gallery filtering built with JavaScript is causing the page to crash. Which part of my code may be causing it?
body: <p>Disclaimer: I know my code is pretty bad. I'm not very experienced with JavaScript yet.</p>
  
  <p>So I built a gallery with filtering using JavaScript and the WP Rest API. I finally have it working, but after a few clicks between the gallery sub-categories, the page starts slowing down and eventually crashes. I imagine I'm doing something really inefficient that is killing the page, but I'm not sure what the main culprit is.</p>
  
  <p>Besides the other obvious issues with how I've written the code, what might be causing it and is there a good way to test performance issues like this?</p>
  
  <p>(Here's a link to a working version of this: <a href="http://victorysurfaces.x10host.com/gallery/" rel="nofollow noreferrer">http://victorysurfaces.x10host.com/gallery/</a>)</p>
  
  <p>Edit: Updated code with fix for extra HTML DOM nodes being added by lightbox code. Didn't fix page crashing issue, unfortunately.</p>
  
  <p>Update: I've noticed that sometimes when I click on a sub-category, it makes more network requests, but sometimes it doesn't. I feel like this might be important.</p>
  
  <p>Update 2: I think it might have something to do with the event listeners I'm adding. Since I'm adding the sub-categories dynamically, I have to add the event listeners after they have been loaded, but the event listeners from the previous run seem to remain, so the number of event listeners just grows and grows. I don't know what to do about that.</p>
  
  <pre><code>&lt;div class="gallery"&gt;
    &lt;div class="medium-wrapper"&gt;
  
      &lt;div class="gallery__filters text-center"&gt;
  
        &lt;div class="gallery__main-filters"&gt;
  
          &lt;button class="category-filter main-category active" data-category="residential"&gt;Residential&lt;/button&gt;
  
          &lt;span&gt;|&lt;/span&gt;
  
          &lt;button class="category-filter main-category" data-category="commercial"&gt;Commercial&lt;/button&gt;
  
        &lt;/div&gt;
  
        &lt;div class="gallery__category-filters"&gt;&lt;/div&gt;
  
      &lt;/div&gt;
  
      &lt;div class="gallery__images"&gt;&lt;/div&gt;
  
    &lt;/div&gt;
  
  &lt;/div&gt;
  
  &lt;script&gt;
  
    /* I'm so sorry for this monstrosity. This was way more complicated than I thought and in the end I just wanted it to work */
  
    jQuery(document).ready(function($) {
      $('.main-category').on('click', function() {
        $('.main-category').removeClass('active');
        $(this).addClass('active');
      });
      initLightbox();
    });
  
    jQuery( function( $ ) {
  
      $.getJSON("/wp-json/wp/v2/gallery-categories", function( data ) {
  
        var currentMainCategory = $('.main-category.active').data('category');
        getSubCategories();
  
        var currentSubCategory = '';
  
        document.querySelectorAll('.main-category').forEach( function(trigger) {
            trigger.addEventListener('click', function() {
              resetCategories($(this).data('category')); }, false);
          });
  
        function getSubCategories() {
          var categoriesArray = [];
          var subCategories = data.map(function(category) {
            if( category.acf.gallery_section.trim().toLowerCase() === currentMainCategory) {
  
              var setCategory = "&lt;button class='category-filter sub-category' data-category='" + category.acf.category_title + "'&gt;" + category.acf.category_title + "&lt;/button&gt;";
  
              categoriesArray.push(setCategory);
            }
          });
          $('.gallery__category-filters').html(categoriesArray);
          getPhotos();
        }
  
        function resetCategories(mainCategoryTitle) {
          currentMainCategory = '';
          currentSubCategory = '';
          $('.sub-category').removeClass('active');
          $('.gallery__category-filters').empty();
          currentMainCategory = mainCategoryTitle;
          getSubCategories();
        }
  
        function setSubCategory() {
          currentSubCategory = document.querySelector('.sub-category.active').dataset.category;
          getPhotos();
        }
  
        var galleryPhotos;
  
        function getPhotos(photos) {
  
          $('.gallery__images').empty();
  
          var mainCategory = currentMainCategory.trim().toLowerCase();
          if( (currentSubCategory !== undefined) &amp;&amp; (currentSubCategory !== '' ) ) {
            var subCategory = currentSubCategory.trim().toLowerCase();
          }
  
          galleryPhotos = data.map(function(category) {
  
            if( category.acf.gallery_section.toLowerCase() === mainCategory ) {
  
              if( subCategory !== '' &amp;&amp; subCategory !== undefined) {
  
                var categoryTitle = category.acf.category_title.toLowerCase().trim();
  
                if( categoryTitle === subCategory ) {
  
                  var galleryCategory = category.acf.gallery_items;
  
                  var categoryPhotos = galleryCategory.map(function(photo) {
  
                    var galleryPhoto = "&lt;div class='gallery__item'&gt;&lt;a class='lightbox-link' href=''&gt;&lt;img class='full-width lightbox-target' src='" + photo.gallery_item_image.sizes.flexible + "' alt='" + photo.gallery_item_image.alt + "'&gt;&lt;/a&gt;&lt;/div&gt;";
  
                    return galleryPhoto;
                  });
  
                  $('.gallery__images').append(categoryPhotos);
                }
  
              } else {
  
                var galleryCategory = category.acf.gallery_items;
  
                var categoryPhotos = galleryCategory.map(function(photo) {
  
                  var galleryPhoto = "&lt;div class='gallery__item'&gt;&lt;a class='lightbox-link' href=''&gt;&lt;img class='full-width lightbox-target' src='" + photo.gallery_item_image.sizes.flexible + "' alt='" + photo.gallery_item_image.alt + "'&gt;&lt;/a&gt;&lt;/div&gt;";
  
                  return galleryPhoto;
                });
  
                $('.gallery__images').append(categoryPhotos);
              }
  
            }
  
          });
  
          $('.sub-category').on('click', function() {
            $('.sub-category').removeClass('active');
            $(this).addClass('active');
            setSubCategory();
          });
  
          checkOrientation();
          handleLightboxUpdate();
  
        }
  
      });
  
    });
  
    function checkOrientation() {
      document.querySelectorAll('.lightbox-target').forEach(function(item) {
        var image = new Image();
        image.src = item.src;
        image.onload = function() {
          if(image.naturalHeight &gt;= image.naturalWidth) {
            item.classList.add('portrait');
          }
        }
      });
    }
  
    function initLightbox() {
      var $overlay = jQuery('&lt;div id="overlay"&gt;&lt;/div&gt;');
      var $container = jQuery('&lt;div class="lightbox"&gt;&lt;a href="javascript:void(0)" class="close"&gt;&amp;times;&lt;/a&gt;&lt;/div&gt;');
      var $image;
      var $imageClone;
  
      jQuery('body').append($overlay);
  
      $overlay.click(function(){
        $overlay.hide();
      });
  
      $overlay.append($container);
    }
  
    function handleLightboxUpdate() {
      document.querySelectorAll('.lightbox-link').forEach( function(trigger) {
        trigger.addEventListener('click', function() {
          event.preventDefault();
          jQuery('.lightbox-image').remove();
  
          $image = jQuery(this).find('.lightbox-target');
  
          $imageClone = $image.clone();
  
          if($imageClone.hasClass('portrait')) {
            $imageClone.addClass('resize-lightbox');
          }
  
          jQuery('#overlay').show();
          //add image to overlay
          $imageClone.addClass('lightbox-image').appendTo('#overlay .lightbox');
        });
      });
    }
  
  &lt;/script&gt;
  </code></pre>