Carla Holcomb
Carla Holcomb

Reputation: 1

Receiving Undefined Error for Javascript Program when Updating for CRUD Frontend

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());
  }
}

Upvotes: 0

Views: 181

Answers (1)

tao
tao

Reputation: 90217

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())
}

Upvotes: 1

Related Questions