Monday, 11 January 2021

JavaScript application bug: calling a function inside another throws a "Maximum call stack size exceeded" error

I have a list of Bootstrap 4 cards, displayed on the 12 columns grid.

I have made a filter by year and a search-box. As can be seen in the example below, the search expression is matched against the title of each card and the selected year is matched against the value of the data-year attribute.

class CardsFilteringComponent {
  constructor() {
    this.filterableCardsList = document.querySelector('.filterableItems');
    this.items = this.filterableCardsList.querySelectorAll('.card-container');
    this.searchBox = this.filterableCardsList.querySelector('#searchBoxInput');
    this.searchBtn = this.filterableCardsList.querySelector('#searchBoxSubmitBtn');
    this.selectBox = this.filterableCardsList.querySelector('#selectYear');
    this.isHiddenClass = 'd-none';
  }

  handleSearchButtonClick(target) {
    // call the filter function
    //this.handleSelectChange(target);
    const expression = this.searchBox.value.toLowerCase();

    [...this.items].forEach(item => {
      const cardTitle = item.querySelector('.title').textContent;
      const showItem = cardTitle.toLowerCase().indexOf(expression) > -1;
      const method = showItem ? 'remove' : 'add';
      item.classList[method](this.isHiddenClass);
    });
  }

  handleSelectChange(target) {
    // call the search function
    //this.handleSearchButtonClick(target);
    const {value } = target;

    [...this.items].forEach(item => {
      const year = item.dataset.year;
      const showItem = year === value || value === 'ALL';
      const method = showItem ? 'remove' : 'add';
      item.classList[method](this.isHiddenClass);
    });
  }

  init() {
    this.searchBtn.addEventListener('click', () => {
      this.handleSearchButtonClick();
    });

    this.searchBox.addEventListener('keyup', e => {
      if (e.keyCode === 13) {
        this.handleSearchButtonClick();
      }
    });

    this.selectBox.addEventListener('change', e => {
      this.handleSelectChange(e.target);
    });
  }
}
const cardsFilteringComponent = new CardsFilteringComponent();
cardsFilteringComponent.init();
.cards-grid>[class*='col-'] {
  display: flex;
  flex-direction: column;
  margin-bottom: 25px;
}

.cards-grid .card {
  flex-grow: 1;
  display: flex;
  flex-direction: column;
  background: #fff;
  border-radius: 2px;
  transition: all 0.3s ease-in-out;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.11);
}
<link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"/>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css" rel="stylesheet"/>
<div class="container-fluid">
  <div class="filterableItems my-4">
    <div class="input-group mb-2">
      <input type="text" class="form-control" placeholder="Search" id="searchBoxInput">
      <div class="input-group-append">
        <button class="btn btn-secondary" id="searchBoxSubmitBtn" type="button">
          <i class="fa fa-search"></i>
        </button>
      </div>
    </div>
    <div class="form-group mb-2">
      <select id="selectYear" class="form-control">
        <option value="ALL" selected>All</option>
        <option value="2019">2019</option>
        <option value="2020">2020</option>
        <option value="2021">2021</option>
      </select>
    </div>
    <div class="row cards-grid">
      <div class="card-container col-xs-12 col-md-4" data-year="2019">
        <div class="card">
          <div class="cardImage">
            <img src="https://picsum.photos/1200/800/?gravity=north" alt="Lorem ipsum dolor" class="img-fluid">
          </div>
          <div class="cardContent p-3">
            <h5 class="title">Lorem ipsum dolor</h5>
            <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Repellat voluptates modi hic molestias quam doloremque. Accusantium odio blanditiis, amet placeat distinctio, quam magni, nobis perferendis error dicta perspiciatis quod delectus.</p>
          </div>
          <div class="mt-auto p-3 text-right text-muted small">Published on January 2<sup>nd</sup> 2019</div>
        </div>
      </div>
      <div class="card-container col-xs-12 col-md-4" data-year="2020">
        <div class="card">
          <div class="cardImage">
            <img src="https://picsum.photos/1200/800/?gravity=south" alt="In, quod adipisci" class="img-fluid">
          </div>
          <div class="cardContent p-3">
            <h5 class="title">In, quod adipisci</h5>
            <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quia eaque qui ipsa quod facere autem voluptatem.</p>
          </div>
          <div class="mt-auto p-3 text-right text-muted small">Published on January 3<sup>rd</sup> 2020</div>
        </div>
      </div>
      <div class="card-container col-xs-12 col-md-4" data-year="2021">
        <div class="card">
          <div class="cardImage">
            <img src="https://picsum.photos/1200/800/?gravity=west" alt="Pariatur, sit, dolor" class="img-fluid">
          </div>
          <div class="cardContent p-3">
            <h5 class="title">Pariatur, sit, dolor</h5>
            <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Dolorum eaque repellat cumque pariatur dolores enim quibusdam nemo distinctio, dolore incidunt cupiditate ea excepturi est architecto amet tempore voluptatibus alias doloremque.</p>
          </div>
          <div class="mt-auto p-3 text-right text-muted small">Published on January 2<sup>nd</sup> 2021</div>
        </div>
      </div>
    </div>
  </div>
</div>

The problem

Besides making these two filters work on their own, I need to make them work together: if I do a search followed by a filtering, only the search results should be filtered by year.

For this purpose I call each method inside the other one, as can be seen in the commented out lines //this.handleSearchButtonClick(target) and //this.handleSelectChange(target).

This results in the error:

Uncaught RangeError: Maximum call stack size exceeded

See the error in this fiddle.

I am looking for an elegant, non-hack, solution.

Questions:

  1. Where is my mistake?
  2. What is the optimal, non-hack fix?


from JavaScript application bug: calling a function inside another throws a "Maximum call stack size exceeded" error

No comments:

Post a Comment