Jammming - Saving playlist throws 400 Bad request

I’ve been stuck on the Jammming project for a few days now. Went though the walkthrough (not very helpful for that matter) and all the forum posts about the same issue I have, tried every suggested tip, compared my solution to that of others, but I’m not seeing any improvement.

I have two issues, the first one is the subject of my post as it’s the most blocking. Any help or push in the right direction would be greatly appreciated. :pray:

Issue #1

In savePlaylist(), the last POST request to add the tracks to the Spotify playlist throws a 400 Bad Request error.
The console logs:

As far as I can judge, the format of the request seems correct. The two previous requests of savePlaylist() work fine.

Now, tbh, I only formatted the URL this way because of the hint in the project step and also saw that other learners made it work this way. But initially, I was not referencing the userID in the URL, because the Spotify Docs for adding items to a playlist indicate the format should be https://api.spotify.com/v1/playlists/${playlistID}/tracks
I’m getting the same 400 error with both formats, but still don’t understand why we use the format advised in the hint.

Back to the error. In the Network tab I’m observing that there are apparently two requests being made with the same URL. One returns 200 and the other returns 400, as shown below:

How come there are two requests being made and why is there one apparently correct and the other not?

Things I’ve tried to troubleshoot the issue, all to no avail:

  • Replacing this.getAccessToken() with Spotify.getAccessToken()
  • Writing the function with a .then syntax instead of async/await
  • Adding a json: true parameter to the body of my request
  • Double checked the format/syntax of my headers
  • Logging in the expiresIn value → evaluates to 3600 as expected

Issue #2

When I refresh the page (say after I changed something in the code to test) and submit a search, the page shows the search results and then reloads after one second, clearing everything and going back to the initial page. No error is logged to the console. I feel like this might have to do with the code for clearing the parameters, HOWEVER, when I try searching again after this behaviour, everything works fine. If I refresh the page again, the same problem occurs. I don’t understand what’s causing this behaviour when searching after a refresh, but not causing it for the next search…

My code

Spotify.js

const client_ID = '' // removed for this post
const redirect_URI = 'http://localhost:3000/';

let accessToken;

const Spotify = {
  getAccessToken() {
    // Check if accessToken is already set
    if (accessToken) {
      return accessToken;
    }

    const accessTokenMatch = window.location.href.match(/access_token=([^&]*)/);
    const expiresInMatch = window.location.href.match(/expires_in=([^&]*)/);

    if (accessTokenMatch && expiresInMatch) {
      accessToken = accessTokenMatch[1];
      const expiresIn = Number(expiresInMatch[1]);

      // Clear the parameters from the URL, so the app doesn’t try grabbing the access token after it has expired
      window.setTimeout(() => (accessToken = ''), expiresIn * 1000);
      window.history.pushState('Access Token', null, '/');

      return accessToken;
    } else {
      window.location = `https://accounts.spotify.com/authorize?client_id=${client_ID}&response_type=token&scope=playlist-modify-public&redirect_uri=${redirect_URI}`;
    }
  },

  async search(term) {
    accessToken = this.getAccessToken();
    const response = await fetch(
      `https://api.spotify.com/v1/search?type=track&q=${term}`,
      {
        headers: {
          Authorization: `Bearer ${accessToken}`,
        },
      }
    );

    const jsonResponse = await response.json();

    if (!jsonResponse.tracks) {
      return [];
    }
    return jsonResponse.tracks.items.map((track) => ({
      id: track.id,
      name: track.name,
      artist: track.artists[0].name,
      album: track.album.name,
      URI: track.uri,
    }));
  },

  async savePlaylist(playlistName, trackURIs) {
    if (!playlistName || !trackURIs.length) {
      return;
    }

    accessToken = this.getAccessToken();
    const headers = {
      Authorization: `Bearer ${accessToken}`,
      'Content-Type': 'application/json',
    };
    let userID;

    try {
      // Get current user's Spotify username (user ID)
      const userID_response = await fetch(`https://api.spotify.com/v1/me`, {
        headers: headers,
      });

      const userID_jsonResponse = await userID_response.json();
      userID = userID_jsonResponse.id;

      // Create new playlist in user's account and return playlist ID
      const playlistID_response = await fetch(
        `https://api.spotify.com/v1/users/${userID}/playlists`,
        {
          headers: headers,
          method: 'POST',
          body: JSON.stringify({
            name: playlistName,
          }),
        }
      );

      const playlistID_jsonResponse = await playlistID_response.json();
      const playlistID = playlistID_jsonResponse.id;

      // Add trackURIS to the newly created playlist
      return await fetch(
        `https://api.spotify.com/v1/users/${userID}/playlists/${playlistID}/tracks`, // throws 400 error
        {
          headers: headers,
          method: 'POST',
          body: JSON.stringify({
            uris: trackURIs,
          }),
        }
      );
    } catch (err) {
      return console.log(err);
    }
  },
};

export default Spotify;

App.js

import React from 'react';
import './App.css';
import SearchBar from '../SearchBar/SearchBar';
import SearchResults from '../SearchResults/SearchResults';
import Playlist from '../Playlist/Playlist';
import Spotify from '../../util/Spotify';

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      searchResults: [],
      playlistName: 'my Playlist',
      playlistTracks: [],
    };

    this.addTrack = this.addTrack.bind(this);
    this.removeTrack = this.removeTrack.bind(this);
    this.updatePlaylistName = this.updatePlaylistName.bind(this);
    this.savePlaylist = this.savePlaylist.bind(this);
    this.search = this.search.bind(this);
  }

  addTrack(track) {
    // Checking if the saved track is already in the playlist by finding the first saved track that matches the id of the added track. If something is found, then it evaluates to true, otherwise to undefined
    if (
      this.state.playlistTracks.find((savedTrack) => savedTrack.id === track.id)
    ) {
      return;
    }
    this.state.playlistTracks.push(track);
    this.setState({
      playlistTracks: this.state.playlistTracks,
    });
  }

  removeTrack(track) {
    const updatedPlaylist = this.state.playlistTracks.filter(
      (savedTrack) => savedTrack.id !== track.id
    );
    this.setState({
      playlistTracks: updatedPlaylist,
    });
  }

  updatePlaylistName(name) {
    this.setState({
      playlistName: name,
    });
  }

  savePlaylist() {
    const trackURIs = this.state.playlistTracks.map((track) => track.uri);
    Spotify.savePlaylist(this.state.playlistName, trackURIs).then(() => {
      this.setState({
        playlistName: 'New Playlist',
        playlistTracks: [],
      });
    });
  }

  search(searchTerm) {
    Spotify.search(searchTerm).then((searchResults) => {
      this.setState({ searchResults: searchResults });
    });
  }

  render() {
    return (
      <div>
        <h1>
          Ja<span className="highlight">mmm</span>ing
        </h1>
        <div className="App">
          <SearchBar onSearch={this.search} />
          <div className="App-playlist">
            <SearchResults
              searchResults={this.state.searchResults}
              onAdd={this.addTrack}
            />
            <Playlist
              playlistName={this.state.playlistName}
              playlistTracks={this.state.playlistTracks}
              onRemove={this.removeTrack}
              onNameChange={this.updatePlaylistName}
              onSave={this.savePlaylist}
            />
          </div>
        </div>
      </div>
    );
  }
}

export default App;

Thanks for any help!