Jamming - error when trying to save playlist - TypeError: Cannot read property 'then' of undefined

Hi everyone,

I am finally getting to the end of the jamming project, with the help of the forum I was able to correct some of the issues I had with my code but cannot figure out what this one means.Capture

Any pointers would be greatly appreciated. I have checked the forum and the project walkthrough but at this point, I am completely stuck.

const clientId = '';
const redirectUri = 'http://localhost:3000/';
let accessToken;

const Spotify = {
  getAccessToken(){
    if(accessToken){
      return accessToken;
    }

    // check for access token match
    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]);

      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&scope=playlist-modify-public&redirect_uri=${redirectUri}`;
      window.location = accessUrl;
    }
  },
  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
      }));
    });
  },

  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`,
    {
      hearders: 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;

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){
    let tracks = this.state.playlistTracks;
    if (tracks.find(savedTrack => savedTrack.id === track.id)) {
      return;
  }
  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(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(term) {
    Spotify.search(term).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;

Hello, @aurore, and welcome to the forums!

When you see a TypeError: Cannot read property ‘whatever’ of undefined, the place to start looking is where ‘whatever’ is attached to should have been defined. In this case, ‘then’ is being called on something that is undefined. The something is the return value of Spotify.savePlaylist(). So, we need to look at the savePlaylist() function in the Spotify component. I see a typo in that function. If you need a hint, try looking for the word, ‘hearders’. :wink:

2 Likes

Thank you so much @midlindner, I feel a little bit silly now ;-).

1 Like

Hey What a great tip! hope it solves it for me :smiley: Doing the Ravenous part 4 project but it’s probably the same thing for me :smiley:

1 Like

App.js
import React from ‘react’;
import ‘./App.css’;

import Playlist from ‘…/Playlist/Playlist’;
import SearchBar from ‘…/SearchBar/SearchBar’;
import SearchResults from ‘…/SearchResults/SearchResults’;
import Spotify from ‘…/…/util/Spotify’;

class App extends React.Component {
constructor(props) {
super(props);

this.state = {
  searchResults: [],
  playlistName: 'New Playlist',
  playlistTracks: []
};

this.search = this.search.bind(this);
this.addTrack = this.addTrack.bind(this);
this.removeTrack = this.removeTrack.bind(this);
this.updatePlaylistName = this.updatePlaylistName.bind(this);
this.savePlaylist = this.savePlaylist.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;
}

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

render() {
return (


Jammming










);
}
}

export default App;

const clientId = ‘2e1b1f9ef30940c59b06eb9138f748d8’; // Insert client ID here.
const redirectUri = ‘http://localhost:3000/’; // Have to add this to your accepted Spotify redirect URIs on the Spotify API.
let accessToken;

const Spotify = {
getAccessToken() {
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]);
  window.setTimeout(() => accessToken = '', expiresIn * 1000);
  window.history.pushState('Access Token', null, '/'); // This clears the parameters, allowing us to grab a new access token when it expires.
  return accessToken;
} else {
  const accessUrl = `https://accounts.spotify.com/authorize?client_id=${clientId}&response_type=token&scope=playlist-modify-public&redirect_uri=${redirectUri}`;
  window.location = accessUrl;
}

},

search(term) {
const accessToken = Spotify.getAccessToken();
console.log(“acess token is” + accessToken);
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
}));
});
},

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;

I’m facing same error, can you someone help me on this?

I have the same issue, then I tried this:
savePlaylist(name, trackUris) {
if (!name || !trackUris.length) {
return Promise.resolve();
}

Because you cannot return an empty so after search around I found this.
The Promise.resolve() method returns a Promise object that is resolved with a given value. If the value is a promise, that promise is returned; if the value is a thenable (i.e. has a "then" method ), the returned promise will “follow” that thenable, adopting its eventual state; otherwise the returned promise will be fulfilled with the value. This function flattens nested layers of promise-like objects (e.g. a promise that resolves to a promise that resolves to something) into a single layer.

1 Like

Hi, I’m having a similar problem. I believe the problem is that the Playlist ID is coming back as undefined creating the 404 error. This goes back to the savePlaylist function, which is as below.

Can anyone advise where I’m going wrong please?

    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/${userId}/playlists`,
                {
                    headers: headers,
                    method: 'POST',
                    body: JSON.stringify({ name: name })
                }).then(response => response.json()
                ).then(jsonResponse => {
                    const playlist_id = jsonResponse.id;
                    **console.log(`Playlist ID: ${playlist_id}`);**
                    return fetch(`https://api.spotify.com/v1/users/${userId}/playlists/${playlist_id}/tracks`, {
                        headers: headers,
                        method: 'POST',
                        body: JSON.stringify({ uris: trackUris})
                    });
                });
                
        });
    }

The console shows the following:

Spotify.js:63 POST https://api.spotify.com/v1/XYZMYACCOUNTNAMEXYZ/playlists 404
(anonymous) @ Spotify.js:63
Promise.then (async)
savePlaylist @ Spotify.js:60
savePlaylist @ App.js:47
callCallback @ react-dom.development.js:3945
invokeGuardedCallbackDev @ react-dom.development.js:3994
invokeGuardedCallback @ react-dom.development.js:4056
invokeGuardedCallbackAndCatchFirstError @ react-dom.development.js:4070
executeDispatch @ react-dom.development.js:8243
processDispatchQueueItemsInOrder @ react-dom.development.js:8275
processDispatchQueue @ react-dom.development.js:8288
dispatchEventsForPlugins @ react-dom.development.js:8299
(anonymous) @ react-dom.development.js:8508
batchedEventUpdates$1 @ react-dom.development.js:22396
batchedEventUpdates @ react-dom.development.js:3745
dispatchEventForPluginEventSystem @ react-dom.development.js:8507
attemptToDispatchEvent @ react-dom.development.js:6005
dispatchEvent @ react-dom.development.js:5924
unstable_runWithPriority @ scheduler.development.js:646
runWithPriority$1 @ react-dom.development.js:11276
discreteUpdates$1 @ react-dom.development.js:22413
discreteUpdates @ react-dom.development.js:3756
dispatchDiscreteEvent @ react-dom.development.js:5889
Spotify.js:71 Playlist ID: undefined
Spotify.js:72 POST https://api.spotify.com/v1/users/XYZMYACCOUNTNAMEXYZ/playlists/undefined/tracks 404

This solved my issue. Thank you @lehoangnhan149252898 .

return Promise.resolve();