Solution 1 :

const book = Book.findById(id);

returns undefined.

Which means there is no entry with that id. e.target.dataset.id probably has a different value than expected (console.log is your friend).


To guard against this type of error (so your app doesn’t break when you’re using an invalid/non-existent id), you could just wrap next line in a condition:

handleEditClick(e) {
  const id = e.target.dataset.id;
  const book = Book.findById(id);
  if (book) {
     $('#update').html(book.renderUpdateForm());
  }
}

Better checks are whether the result has a function named renderUpdateForm:

if (book && typeof book.renderUpdateForm === 'function') { 
  $('#update').html(book.renderUpdateForm())
}

or if it is a Book instance:

if (book instanceof Book) { 
  $('#update').html(book.renderUpdateForm())
}

Problem :

I created a frontend javascript for my ruby on rails backend and I am receiving the following error when I try to update a book title:

Error: app.js:65 Uncaught TypeError: Cannot read property ‘renderUpdateForm’ of undefined
at App.handleEditClick (app.js:65)

I am receiving books titles and their authors from a backend api.

Here is the code:

    document.addEventListener('DOMContentLoaded', () => {
  const app = new App();
  app.attachEventListeners();
  app.adapter.fetchAuthors().then(app.createAuthors);
  app.adapter.fetchBooks().then(app.createBooks);

});
    class Book {
  constructor(data) {
    this.id = data.id;
    this.title = data.title;
    const author = Author.findById(data.author_id);
    this.author = author.name;
    Book.all.push(this);
  }

  update({ title }) {
    this.title = title;
  }


  renderListItem() {
    return `
    <li>
      <h3>${this.title} - ${this.author}
      <button data-id=${this.id}>update</button>
        <button data-id=${this.id}>delete</button>
      </h3>
    </li>`;
  }

  renderUpdateForm() {
    return `
    <form data-id='${this.id}'>
      <label>Title</label>
      <p>
        <input type="text" value="${this.title}" />
      </p>

      <button type='submit'>Save Book</button>
    </form>
  `;
  }

  static findById(id) {
    return this.all.find(book => book.id === id);
  }
}

Book.all = [];

    class App {
  constructor() {
    this.adapter = new Adapter();

    this.handleEditClick = this.handleEditClick.bind(this);
    this.handleEditClick = this.handleDeleteClick.bind(this);
    this.handleFormSubmit = this.handleFormSubmit.bind(this);
    this.createBooks = this.createBooks.bind(this);
    this.createAuthors = this.createAuthors.bind(this);
    this.addBooks = this.addBooks.bind(this);

  }

  attachEventListeners() {
    $('#books-list').on('click', 'button', this.handleEditClick);
    $('#books-list').on('click', 'button', this.handleDeleteClick);
    $('#update').on('submit', 'form', this.handleFormSubmit);
  }

  createBooks(books) {
    books.forEach(book => {
      new Book(book);
    });
    console.log(this);
    this.addBooks();
  }

  createAuthors(authors) {
    authors.forEach(author => {
      new Author(author);
    });
    console.log(this);
    this.addAuthors();
  }

  addBooks() {
    $('#books-list').empty();
    Book.all.forEach(book => $('#books-list').append(book.renderListItem()));
  }

  addAuthors() {
    $('#authors-list').empty();
    Author.all.forEach(author => $('#authors-list').append(author.renderListItem()));
  }


  handleFormSubmit(e) {
    e.preventDefault();
    const id = e.target.dataset.id;
    const book = Book.findById(id);
    const title = $(e.target)
      .find('input')
      .val();

    const bodyJSON = { title };
    this.adapter.updateBook(book.id, bodyJSON).then(updatedBook => {
      const book = Book.findById(updatedBook.id);
      book.update(updatedBook);
      this.addBooks();
    });
  }

  handleEditClick(e) {
    const id = e.target.dataset.id;
    const book = Book.findById(id);
    $('#update').html(book.renderUpdateForm());
  }

  handleDeleteClick(e) {
    const id = e.target.dataset.id;
    const book = Book.findById(id);
    const title = $(e.target)
      .find('input')
      .val();

    const bodyJSON = { title };
    this.adapter.deleteBook(book.id, bodyJSON);

  }


}


 class Adapter {
  constructor() {
    this.baseUrl = 'http://localhost:3000/api/v1';
    this.headers = {
      'Content-Type': 'application/json',
      Accept: 'application/json'
    };
  }

  fetchBooks() {
    return this.get(`${this.baseUrl}/books`);
  }

  fetchAuthors() {
    return this.get(`${this.baseUrl}/authors`);
  }

  updateBook(id, body) {
    return this.patch(`${this.baseUrl}/books/${id}`, body);
  }

  deleteBook(id, body) {
    return this.delete(`${this.baseUrl}/books/${id}`, body);
  }

  get(url) {
    return fetch(url).then(res => res.json());
  }

  patch(url, body) {
    return fetch(url, {
      method: 'PATCH',
      headers: this.headers,
      body: JSON.stringify(body)
    }).then(res => res.json());
  }

  delete(url, body) {
    return fetch(url, {
      method: 'DELETE',
      headers: this.headers,
      body: JSON.stringify(body)
    }).then(res => res.json());
  }
}

Comments

Comment posted by Carla Holcomb

Tao – Thanks for your help so far. Do you know why the book would not be an instance of the object? I used console.log to ensure that the ID was not nil. The ID is correct, but the const book is not receiving an instance of the object. Thanks.

Comment posted by tao

findById

Comment posted by tao

@Carla, another option is to use loose comparison

By

Leave a Reply

Your email address will not be published. Required fields are marked *