Jammming project : Spotify API errors 403 & 404 leading to a bug in saving a playlist

Save a User’s Playlist - Tasks 89 to 95

I’m posting this first GetHelp request as a newbee in the JS-React development: I just started 3 months ago on Codecademy the web development career path.

In the Jammming project, when I try to save my playlist, clicking on the button “Save to Spotify” it empty my playlist column, leaving me with the PlaylistName to update and a new button “Save to Spotify”.

Meanwhile I got these 2 errors in the console:

If I click again on the SaveToSpotify button (after having renamed the playlist… or not), I have these error displayed in the browser:

Here are my code files:

Spotify.js:

// Spotify Client ID (with few wrong char for this online post)
const clientId = 'ca1cf8c3a2704ad7b8e68e1b774gh31c';

// Spotify redirect URL
const redirectUri = 'http://localhost:3000';

let accessToken ;

const Spotify = {

    // Codecademy tutorial for Access Token @ 1:34:31 here: https://www.youtube.com/watch?v=DH991Dzb9iE&feature=youtu.be&t=7902s 

    getAccessToken() {
        if (accessToken) {
            return accessToken }

        // check for acces token match in url returned after user authentification
        const accessTokenMatch = window.location.href.match(/access_token=([^&]*)/);

        // check for 'expires in' match in url returned after user authentification
        const expiresInMatch = window.location.href.match(/expires_in=([^&]*)/);


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

            // This clear the parameters, allowing us to grab a new access token when it expires
            window.setTimeout( () => accessToken = '', expiresIn * 1000);
            window.history.pushState('Access Token', null, '/');
            
            return accessToken;
        
        } else {
            const accessUrl = `https://accounts.spotify.com/authorize?client_id=${clientId}&response_type=token&redirect_uri=${redirectUri}` ;
            window.location = accessUrl ;
        };
    },

    // Codecademy tutorial for .search() @ 1:34:31 here: https://www.youtube.com/watch?v=DH991Dzb9iE&feature=youtu.be&t=7902s

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

            }).then(jsonResponse => {
                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
                    }
                    
                ));
            });
    },
    

    // CodeAcademy tutorial: .savePlaylist() @ 1:55:00 here https://www.youtube.com/watch?v=DH991Dzb9iE&feature=youtu.be&t=7902s 

    savePlaylist(name, trackUris) {
        if (!name || !trackUris.length) {
            return;
        }

        const accessToken = Spotify.getAccessToken();
        const headers = { Authorization: `Bearer ${accessToken}` };
        let userId;

        return fetch(
                    'https://api.spotify.com/v1/me',
                    { headers: headers }
                    ).then(response => response.json()
                    ).then(jsonResponse => {
                        userId = jsonResponse.id ;                         
                        return fetch(
                                    `https://api.spotify.com/v1/users/${userId}/playlists`,
                                    {
                                        headers: headers,
                                        method: 'POST',
                                        body: JSON.stringify({ name: name })
                                    }
                                    ).then(response => response.json()
                                    ).then(jsonResponse => {
                                        const playlistId = jsonResponse.id;
                                        return fetch(
                                                    `https://api.spotify.com/v1/users/${userId}/playlists/${playlistId}/tracks`,
                                                    {
                                                        headers: headers,
                                                        method: 'POST',
                                                        body: JSON.stringify({ uris: trackUris })
                                                    }
                                                    ) ;
                                    });                      
                    });           
    }

};

export default Spotify ;

In App.js I have :

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: "New 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);
  }

  search(term) {
      Spotify.search(term).then(searchResults => {
        this.setState({searchResults: searchResults})
      })
   }
  
  addTrack(track) {
    let tracks = this.state.playlistTracks;
    if (tracks.find(savedTrack => savedTrack.id === track.id)) {
      return;
    } else {
        tracks.push(track);
        this.setState( { playlistTracks: tracks } );  
        } 
    }

  removeTrack(track) {
    let tracks = this.state.playlistTracks;
    tracks = tracks.filter(currentTrack => currentTrack.id !== track.id);
    this.setState( { playlistTracks: tracks } );
    } 

  updatePlaylistName(newPlaylistName) {
    this.setState( { playlistName: newPlaylistName } );
  }     

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

  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;

In Playlist.js I have:

import React from 'react';
import './Playlist.css';
import TrackList from '../TrackList/TrackList';



class Playlist extends React.Component {

    constructor(props) {
        super(props);
        this.handleNameChange = this.handleNameChange.bind(this);    
    }

    handleNameChange(event) {
        this.props.onNameChange(event.target.value);
    }

    render() {
        return(
            <div className="Playlist">
                <input defaultValue={'New Playlist'} onChange={this.handleNameChange} />
                <TrackList  tracks={this.props.playlistTracks}
                            onRemove={this.props.onRemove}
                            isRemoval={true} />
                <button className="Playlist-save" onClick={this.props.onSave} >SAVE TO SPOTIFY</button>
            </div>

        )
    }
};

export default Playlist;

I’ve looked and hunt for hours for a typo or a silly mistake but I can’t see / understand why my Spotify.savePlaylist is undefined…

Please help, I’m going to become completely mad…

Many many thanks a lot in advance for your help.

1 Like

When I just looked, I got “TypeError: Cannot read property ‘then’ of undefined” when I try and save an empty Playlist which makes sense.

So I have added a check which pops an alert window if trackUris is empty, to my savePlaylist().

You do not need the popup, just stopping the Spotify.savePlaylist() from running by checking is enough to stop it faulting.

I think you may have other problems, but this problem is not helpful.

savePlaylist = () => {
    const trackUris = this.state.playlistTracks.map((track) => track.uri);
    
	if (trackUris && trackUris.length) {
		Spotify.savePlaylist(this.state.playlistName, trackUris).then(() => {
			this.setState({
				playlistName: "New Playlist",
				playlistTracks: [],
			});
		});
	} else {
		alert("Playlist empty");
	}
};
1 Like

Thanks a lot for your help Nessie.
I’ve added your if/else check and it’s actually throwing me an alert popup saying my playlist is empty.
I still have my 404 and 403 errors but the browser error don’t popup anymore.

What’s crazy is that I ended strictly copying every single line of code in the different steps of codecademy video tutorial…

I see in your second screenshot that interpolated http:// URL string has an undefined value which it should be getting back from querying Spotify in the Spotify.js and you also have 403 forbidden errors which are usually security (Authentication) related.

Your Spotify.JS looks ok to me, but I don’t know if you have been putting console.log’s on the input and output variables used by each fetch() stage to see if you are missing data at any point.

I think you may have a problem with Spotify itself tbh.

If I had this problem I would be deleting the Spotify App endpoint on the Dashboard and recreating another one and even creating an alternative Spotify account.

I copied your Spotify.js into a new file on my project and I was getting errors.
I found it was being caused the redirectUri;

// Spotify redirect URL
const redirectUri = 'http://localhost:3000';

It would not work until it had a trailing forward slash added at the end of the URL string (my version had it);

const redirectUri = "http://localhost:3000/";

Nice logic @nessie2015! Helped clear up the error I was running into.

Cheers!