javascriptjqueryarraysajaxspotify

How to remove null rows from items array sent by Spotify API


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:

Screenshot of Chrome console


Solution

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