I'm trying to implement an autocomplete feature to get suggestions of playlists from the Spotify API. I'm able to connect and get a response from Spotify, but some of the rows in their response are sometimes null and that makes things break. Is it possible to delete those null rows before attempting to map the results?
The error that I sometimes get says: Cannot read properties of null (reading 'name'). If I validate for 'name', then I get the same error for 'images' or 'owner'. So I might as well just get rid of the whole null row, but I'm not sure how to do that.
I'm using the necessary scopes, according to documentation:
$options = [
'scope' => [
'user-read-email',
'user-read-private',
'user-read-recently-played',
'user-library-modify',
'playlist-read-private',
'playlist-read-collaborative',
'playlist-modify-private',
'playlist-modify-public',
'user-follow-modify',
],
'state' => $state,
];
This is my script:
<script>
// Autocomplete for playlist lookup (step 1 of form)
$(function() {
$("#playlist-link").autocomplete({
source: function(request, response) {
// Replace with your Spotify API access token
const accessToken = "<?php echo $_SESSION['access_token'] ?>";
$.ajax({
url: "https://api.spotify.com/v1/search",
headers: {
"Authorization": "Bearer " + accessToken
},
data: {
q: request.term,
type: "playlist",
limit: 10 // Adjust the number of results as needed
},
success: function(data) {
// Can I delete the 'items' rows that are null here? If so, how??
if (data.playlists && data.playlists.items) {
const playlists = data.playlists.items.map(function(playlist) {
return {
label: playlist.name, // Displayed in the autocomplete list
value: playlist.name, // Value inserted into the input field
id: playlist.id, // Store the playlist ID for later use
images: playlist.images, // store images for display
owner: playlist.owner.display_name // store owner name
};
});
response(playlists);
} else {
response([]); // Return an empty array if no results are found
}
},
error: function(xhr, status, error) {
console.error("Spotify API error:", error);
response([]); // Handle errors gracefully
}
});
},
minLength: 3, // Start searching after 3 characters
select: function(event, ui) {
// Handle the selected playlist (e.g., store the ID)
console.log("Selected playlist ID:", ui.item.id);
// You can store the playlist ID in a hidden field or use it directly
$("#playlist-id").val(ui.item.id); // Example: storing in a hidden input
},
open: function() {
// Style the dropdown list (optional)
$(".ui-autocomplete").css("z-index", 1000); // Ensure it's on top
},
focus: function(event, ui) {
// Prevent the default behavior (inserting the value on focus)
event.preventDefault();
}
}).autocomplete("instance")._renderItem = function(ul, item) {
// Custom rendering of each item in the dropdown
const image = item.images && item.images.length > 0 ? item.images[0].url : "placeholder.jpg"; // Use a placeholder if no image
return $("<li>")
.append(
"<div>" +
"<img src='" + image + "' style='width: 50px; height: 50px; margin-right: 10px; vertical-align: middle;'/>" +
"<span style='vertical-align: middle;'>" + item.label + "</span>" +
"<br> <span style='font-size: 12px; color: grey; vertical-align: middle;'>By: " + item.owner + "</span>" +
"</div>"
)
.appendTo(ul);
};
});
</script>
And this is what I see in the console:
Filter out the null items before calling .map()
.
$(function() {
$("#playlist-link").autocomplete({
source: function(request, response) {
// Replace with your Spotify API access token
const accessToken = "<?php echo $_SESSION['access_token'] ?>";
$.ajax({
url: "https://api.spotify.com/v1/search",
headers: {
"Authorization": "Bearer " + accessToken
},
data: {
q: request.term,
type: "playlist",
limit: 10 // Adjust the number of results as needed
},
success: function(data) {
// Can I delete the 'items' rows that are null here? If so, how??
if (data.playlists && data.playlists.items) {
const playlists = data.playlists.items.filter(i => i).map(playlist => ({
label: playlist.name, // Displayed in the autocomplete list
value: playlist.name, // Value inserted into the input field
id: playlist.id, // Store the playlist ID for later use
images: playlist.images, // store images for display
owner: playlist.owner.display_name // store owner name
}));
response(playlists);
} else {
response([]); // Return an empty array if no results are found
}
},
error: function(xhr, status, error) {
console.error("Spotify API error:", error);
response([]); // Handle errors gracefully
}
});
},
minLength: 3, // Start searching after 3 characters
select: function(event, ui) {
// Handle the selected playlist (e.g., store the ID)
console.log("Selected playlist ID:", ui.item.id);
// You can store the playlist ID in a hidden field or use it directly
$("#playlist-id").val(ui.item.id); // Example: storing in a hidden input
},
open: function() {
// Style the dropdown list (optional)
$(".ui-autocomplete").css("z-index", 1000); // Ensure it's on top
},
focus: function(event, ui) {
// Prevent the default behavior (inserting the value on focus)
event.preventDefault();
}
}).autocomplete("instance")._renderItem = function(ul, item) {
// Custom rendering of each item in the dropdown
const image = item.images && item.images.length > 0 ? item.images[0].url : "placeholder.jpg"; // Use a placeholder if no image
return $("<li>")
.append(
"<div>" +
"<img src='" + image + "' style='width: 50px; height: 50px; margin-right: 10px; vertical-align: middle;'/>" +
"<span style='vertical-align: middle;'>" + item.label + "</span>" +
"<br> <span style='font-size: 12px; color: grey; vertical-align: middle;'>By: " + item.owner + "</span>" +
"</div>"
)
.appendTo(ul);
};
});