Reputation: 17
I'm working on a book finder app, using the Google Books API that pulls the search results. On the first search, I am seeing the below error in the console. Every subsequent search after the first, pulls the search terms as it should.
Have I got an undefined property somewhere? Or is there an array that is not iterating correctly?
Error:
main.js:65 Uncaught TypeError: Cannot read property '1' of undefined
at displayResults (main.js:65)
at Object.success (main.js:35)
at i (jquery.min.js:2)
at Object.fireWith [as resolveWith] (jquery.min.js:2)
at A (jquery.min.js:4)
at XMLHttpRequest.<anonymous> (jquery.min.js:4)
JS file:
$(document).ready(function() {
var item, title, author, publisher, bookLink, bookImg;
var outputList = document.getElementById("list-output");
var bookUrl = "https://www.googleapis.com/books/v1/volumes?q=";
var apiKey = "key=AIzaSyDtXC7kb6a7xKJdm_Le6_BYoY5biz6s8Lw";
var placeHldr = '<img src="https://via.placeholder.com/150">';
var searchData;
//listener for search button
$("#search").submit(function() {
outputList.innerHTML = ""; //empty html output
document.body.style.backgroundImage = "url('')";
searchData = $("#search-box").val();
console.log(searchData);
//handling empty search input field
if(searchData === "" || searchData === null) {
displayError();
}
else {
// console.log(searchData);
// $.get("https://www.googleapis.com/books/v1/volumes?q="+searchData, getBookData()});
$.ajax({
url: bookUrl + searchData,
dataType: "json",
success: function(response) {
console.log(response);
if (response.totalItems === 0) {
alert("no result!.. try again");
}
else {
$("#title").animate({'margin-top': '5px'}, 1000); //search box animation
$(".book-list").css("visibility", "visible");
displayResults(response);
}
},
error: function () {
alert("Something went wrong.."+"Try again!");
}
});
}
$("#search-box").val(""); //clear search box
});
/*
* function to display result in index.html
* @param response
*/
function displayResults(response) {
for (var i = 0; i < response.items.length; i+=2) {
item = response.items[i];
title1 = item.volumeInfo.title;
author1 = item.volumeInfo.authors;
publisher1 = item.volumeInfo.publisher;
bookLink1 = item.volumeInfo.previewLink;
bookIsbn = item.volumeInfo.industryIdentifiers[1].identifier;
bookImg1 = (item.volumeInfo.imageLinks) ? item.volumeInfo.imageLinks.thumbnail : placeHldr ;
item2 = response.items[i+1];
title2 = item2.volumeInfo.title;
author2 = item2.volumeInfo.authors;
publisher2 = item2.volumeInfo.publisher;
bookLink2 = item2.volumeInfo.previewLink;
bookIsbn2 = item2.volumeInfo.industryIdentifiers[1].identifier;
bookImg2 = (item2.volumeInfo.imageLinks) ? item2.volumeInfo.imageLinks.thumbnail : placeHldr ;
// in production code, item.text should have the HTML entities escaped.
outputList.innerHTML += '<div class="row mt-4">' +
formatOutput(bookImg1, title1, author1, publisher1, bookLink1, bookIsbn) +
formatOutput(bookImg2, title2, author2, publisher2, bookLink2, bookIsbn2) +
'</div>';
console.log(outputList);
}
}
/*
* card element formatter using es6 backticks and templates (indivial card)
* @param bookImg title author publisher bookLink
* @return htmlCard
*/
function formatOutput(bookImg, title, author, publisher, bookLink, bookIsbn) {
var viewUrl = 'book.html?isbn='+bookIsbn; //constructing link for bookviewer
var htmlCard = `<div class="col-lg-6">
<div class="card" style="">
<div class="row no-gutters">
<div class="col-md-4">
<img src="${bookImg}" class="card-img" alt="...">
</div>
<div class="col-md-8">
<div class="card-body">
<h5 class="card-title">${title}</h5>
<p class="card-text">Author: ${author}</p>
<p class="card-text">Publisher: ${publisher}</p>
<a target="_blank" href="${viewUrl}" class="btn btn-secondary">Read Book</a>
</div>
</div>
</div>
</div>
</div>`;
return htmlCard;
}
//handling error for empty search box
function displayError() {
alert("Search term can't be empty!");
}
});
Upvotes: -2
Views: 221
Reputation: 1448
It seems that you assume that all the object properties you access in displayResults(response)
exist. In my experience when dealing with 3rd party APIs you can never be sure. It would be better to check each property before accessing it to avoid errors.
This SO answer has some good starting points: How to check if object property exists with a variable holding the property name?
Copied from that answer:
var myProp = 'prop';
if(myObj.hasOwnProperty(myProp)){
alert("yes, i have that property");
}
Or
var myProp = 'prop';
if(myProp in myObj){
alert("yes, i have that property");
}
Or
if('prop' in myObj){
alert("yes, i have that property");
}
Note that hasOwnProperty
doesn't check for inherited properties, whereas in
does. For example 'constructor' in myObj
is true, but myObj.hasOwnProperty('constructor')
is not.
Here is another useful article on that topic: https://dmitripavlutin.com/7-tips-to-handle-undefined-in-javascript/#22-accessing-non-existing-property.
From there:
Fortunately, JavaScript offers a bunch of ways to determine if the object has a specific property:
obj.prop !== undefined: compare against undefined directly
typeof obj.prop !== 'undefined': verify the property value type
obj.hasOwnProperty('prop'): verify whether the object has an own property
'prop' in obj: verify whether the object has an own or inherited property
My recommendation is to use in operator. It has a short and sweet syntax. in operator presence suggests a clear intent of checking whether an object has a specific property, without accessing the actual property value.
Upvotes: 0