javascripthtmlcssfiltersearchbar

search bar doesn't filter because of Uncaught TypeError: Cannot read properties of undefined (reading 'toLowerCase')


So, this is my first time trying to write a functional search bar. Here is my html, css, and js code for writing a simple search bar. HTML:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script src="search-script.js" defer></script>
    <title>Document</title>
    <link rel="stylesheet" href="search-styles.css" />
  </head>
  <body>
    <div class="search-wrapper">
      <label for="search">Search Books</label>
      <input type="search" id="search" data-search />
    </div>
    <div class="book-cards" data-book-cards-container>
      <template data-book-template>
        <div class="card">
          <div class="header" data-header></div>
          <div class="body" data-body></div>
        </div>
      </template>
    </div>
  </body>
</html>

CSS:

.search-wrapper {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}
input {
  font-size: 1rem;
}
.book-cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 0.25rem;
  margin-top: 1rem;
}

/* animated card styling video od web dev simplified */
.card {
  border: 1px solid black;
  background-color: white;
  padding: 0.5rem;
}

.card > .header {
  margin-bottom: 0.25rem;
}

.card > .body {
  font-size: 0.8rem;
  color: #777;
}
.hide {
  display: none;
}

JavaScript:

const bookCardTemplate = document.querySelector("[data-book-template]");
const bookCardContainer = document.querySelector("[data-book-cards-container]");
const searchInput = document.querySelector("[data-search]");

let books = [];

searchInput.addEventListener("input", (e) => {
  const value = e.target.value.toLowerCase();
  books.forEach((book) => {
    const isVisible =
      book.book_name.toLowerCase().includes(value) ||
      book.author_name.toLowerCase().includes(value);
    book.element.classList.toggle("hide", !isVisible);
  });
});

fetch("http://localhost:3000/books")
  .then((res) => res.json())
  .then((data) => {
    books = data.map((book) => {
      const card = bookCardTemplate.content.cloneNode(true).children[0];
      const header = card.querySelector("[data-header]");
      const body = card.querySelector("[data-body]");
      header.textContent = book.book_name;
      body.textContent = book.author_name;
      bookCardContainer.append(card);
      return {
        title: book.book_name,
        author: book.author_name,
        element: card,
      };
    });
  });

It connects to the db.json file via mock API:

{
  "books": [
    {
      "id": 0,
      "book_name": "To Kill a Mockingbird",
      "author_name": "Harper Lee",
      "rating": 4.3,
      "rating_count": 3252349,
      "price": 11.99
    },
    {
      "id": 1,
      "book_name": "1984",
      "author_name": "George Orwell",
      "rating": 4.28,
      "rating_count": 2743585,
      "price": 10.99
    }
]}

Now, when running this code, all the cards are shown but when typing in the search bar, they are not filtered. So, the search bar doesn't work and here is the error of the console:

search-script.js:11 Uncaught TypeError: Cannot read properties of undefined (reading 'toLowerCase')
    at search-script.js:11:22
    at Array.forEach (<anonymous>)
    at HTMLInputElement.<anonymous> (search-script.js:9:9)

If the error message "Cannot read properties of undefined (reading 'toLowerCase')" means that the variable book is undefined when I am trying to call the toLowerCase() method on it (because the books array is empty when the searchInput.addEventListener() event listener is first attached), then initializing the books array with some data before attaching the event listener (by calling the fetch() function to get the data from the API and then assigning the results to the books array) should fix the problem, but it does not. Because when I updated the js code (I put the updated code below), I get this error in console:

search-script.js:18 Uncaught TypeError: Cannot read properties of undefined (reading 'classList')
    at search-script.js:18:22
    at Array.forEach (<anonymous>)
    at HTMLInputElement.<anonymous> (search-script.js:14:13)

The updated JS code that resulted the above error:

const bookCardTemplate = document.querySelector("[data-book-template]");
const bookCardContainer = document.querySelector("[data-book-cards-container]");
const searchInput = document.querySelector("[data-search]");

let books;

fetch("http://localhost:3000/books")
  .then((res) => res.json())
  .then((data) => {
    books = data;

    searchInput.addEventListener("input", (e) => {
      const value = e.target.value.toLowerCase();
      books.forEach((book) => {
        const isVisible =
          book.book_name.toLowerCase().includes(value) ||
          book.author_name.toLowerCase().includes(value);
        book.element.classList.toggle("hide", !isVisible);
      });
    });
  });

I know this problem may sound silly and I have read the pages with similar questions, but their problems seem different to me and I really don't know how to make my search bar work. So thanks for any help here. Also, isn't any easier way to write a search bar like an already tested component or something like that?


Solution

  • Your event handler should be like this:

    searchInput.addEventListener("input", function (e) {
      const value = e.target.value.toLowerCase();
      books.forEach((book) => {
        const isVisible =
          book.title.toLowerCase().includes(value) ||
          book.author.toLowerCase().includes(value);
    
        book.element.classList.toggle("hide", !isVisible);
      });
    });
    

    There is no book_name or author_name in the book object because you changed their names when setting books values.