Reputation: 1
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
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