Jamming - feature request - - playlistList - Uncaught TypeError: Cannot read property 'then' of undefined

I’m having trouble with the ‘Feature Request’ advanced project (building on the Jammming project)n - hope someone can help me out :slight_smile:

This is what I am trying to achieve:

Base code - which works - from the original jammming project

WIP edited code for Feature Request - breaks the website

Errors in console
On loading the site, I get this warning:

index.js:1  Warning: Playlist contains an input of type undefined with both value and defaultValue props. Input elements must be either controlled or uncontrolled (specify either the value prop, or the defaultValue prop, but not both). Decide between using a controlled or uncontrolled input element and remove one of these props. More info: https://reactjs.org/link/controlled-components

After having used the app (search, logged into Spotify, added tracks to playlist, edited playlist name, and HIT “Save to spotify button”, this is when the whole website breaks with an error message in browser and console:

Uncaught TypeError: Cannot read property 'then' of undefined
    at App.savePlaylist (App.js:51)

This error message is repeated twice in console.

EDIT: For clarity: while the website breaks (unusable: error) - the app does save the playlist to the user’s spotify account before the website breaks.

Code snippets (see above links if want more)
App.js

savePlaylist() {
    console.log(`line 45 App.js`); //printing - before error
    let trackUris = this.state.playlistTracks.map((track) => track.uri);
    console.log(`line 47 App.js ${trackUris}`); // printing before error
    if (trackUris && trackUris.length) {
      console.log(`line 49 App.js ${trackUris} and ${trackUris.length} and ${this.state.playlistName}`); //printing before error
      // below line is the problem 'Uncaught TypeError Cannot read property 'then' of undefined
      Spotify.savePlayList(this.state.playlistName, trackUris).then(() => {
        console.log(`line 52 App.js ${trackUris}`); //not printing
        this.setState({
          playlistName: "New Playlist Name",
          playlistTracks: [],
        });
      });
    } else {
      alert("Your playlist is empty! Please add tracks.");
    }
  }

The only code I’ve significantly altered from the working code of the main Jammming project is in the ./src/util/Spotify.js file where I refactored Spotify.savePlayList() to separate the getting of the userId into new Spotify.getCurrentUserId() method.

I didn’t get any further in my Feature Request plan than this step.

There have been minor changes to run the code locally on localhost:3000 (when I’d previously changed the homepage and redirectUri to be the web address of the GitHub pages deployment).

Spotify.js

 getCurrentUserId() {
    if (userId) {
      return userId;
    }

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

    return fetch("https://api.spotify.com/v1/me", {
      headers: headers,
    })
      .then((response) => response.json())
      .then((jsonResponse) => {
        userId = jsonResponse.id;
        console.log(`line 45 ${userId}`); //prints after error messages
         return userId;
      })
      .catch(function (err) {
        console.log("Fetch problem line 49: " + err.message); //doesn't print
      });;
  },
savePlayList(name, trackUris) {
    if (!name || !trackUris.length) {
      return;
    }

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

    Spotify.getCurrentUserId().then((response) => {
      userId = response;
      console.log(`line 91 ${userId}`); // prints after error messages
      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;
          console.log(`line 100 ${playlistId}`); //prints after error messages
          return fetch(
            `https://api.spotify.com/v1/users/${userId}/playlists/${playlistId}/tracks`,
            {
              headers: headers,
              method: "POST",
              body: JSON.stringify({ uris: trackUris }),
            }
          );
        })
        .catch(function (err) {
          console.log("Fetch problem: " + err.message); //doesn't print
        });
    });
},

Troubleshooting
I have read other posts here on codecademy and searched online, and the closest similarity that I can find to my problem is to this post - which suggests it may be a return issue, but I can’t see where I would need to put another return.

Throughout my code I have used console.log to identify what is and isn’t running and logging.

  • logs App.js lines 45, 47, and 49 fine, testing trackUris, trackUris.length, and this.state.playlistName
  • error is hit at App.js line 51, and line 52 doesn’t log
  • after the error is thrown, Spotify.js lines 48, 91 and 100 are logged, with userId and playlistId
  • I have tried a few tweaks after reading other info - adding and removing ‘return’ statements, reordering the promise/then chain, and calling of methods. It’s hard to know if I fixed one thing and broke another!

Thanks for any advice you can give.

Hello,

You’ve done a great job documenting the errors, your troubleshooting steps, and making the code available in the post and as a branch in your GitHub repository. Well done!


This one is just a warning, but it’s complaining about your code here from Playlist.js

<input
  value={this.props.playlistName}
  defaultValue={this.props.playlistName}
  onChange={this.handleNameChange}
/>

The full warning is:

Warning: Playlist contains an input of type undefined with both value and defaultValue props. Input elements must be either controlled or uncontrolled (specify either the value prop, or the defaultValue prop, but not both). Decide between using a controlled or uncontrolled input element and remove one of these props. More info: https://reactjs.org/link/controlled-components

It doesn’t like that you’re using both value (controlled) and defaultValue (uncontrolled) at the same time. I’d recommend getting rid of the defaultValue line so that it can be a controlled input element. That way the value of the input box can be set by the prop. defaultValue wouldn’t be as useful since we aren’t just trying to set a default for a user to possibly override.


Your suspicions about it being a return issue are correct.

Here are some questions to ask yourself:

  • What does Spotify.savePlayList() return right now?
  • What does Spotify.getCurrentUserId() return?
  • Why does chaining a .then() to a call to Spotify.getCurrentUserId() work, but chaining a .then() to a call to Spotify.savePlayList() result in an error about not being able to read the property then of undefined

Hints:

  • Chaining .then() to a function/method call requires that it returns a Promise.
  • If a function/method doesn’t return something, then JavaScript will use the value undefined
    .
Click here if you'd like more hints

You removed the return statement from your Spotify.savePlayList() method while you were refactoring to include the .getCurrentUserId() method. This means that when you call Spotify.savePlayList() from your savePlaylist() method in App.js, it attempts to chain .then() on undefined.

Spotify.getCurrentUserId() returns a Promise, which is why you could chain a .then() to it from Spotify.savePlayList(). You need to add a return before your call to Spotify.getCurrentUserId() so that:

    return Spotify.getCurrentUserId().then((response) => {
      userId = response;
      console.log(`line 91 ${userId}`); // prints after error messages

now Spotify.savePlayList() will return a Promise too, so .then() can be chained to it. JavaScript will no longer use a default of undefined.

2 Likes

Thank you so much @selectall!

Those questions really helped me focus on the right bit, think hard about my code from a different perspective, and not only did I solve the issue - but I did it without reading your hint!

(Was seriously tempted to read the hint, however realising that I need to understand this topic better it was worth trying first).

1 Like

You’re welcome! I’m very happy to hear that!