Jamming project, step 88

Link: https://www.codecademy.com/paths/web-development/tracks/front-end-applications-with-react/modules/jammming/projects/jammming-prj

I am stuck on step 88 of Jamming project. It requires me to update state of App.js with ‘the value resolved from Spotify.search() ‘s promise.’ I receive the correct value inside of Spotify.js function but I don’t know how to pass it to the App.js state.

Here is my code:
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.addTrack = this.addTrack.bind(this);
    this.removeTrack = this.removeTrack.bind(this);
    this.changePlaylistName = this.changePlaylistName.bind(this);
    this.savePlaylist = this.savePlaylist.bind(this);
    this.search = this.search.bind(this);

    this.state = {
      searchResults: [{
        id: 1,
        name: 'T.N.T',
        artist: 'AC/DC',
        album: 'T.N.T',
        uri: ''
      },
      {
        id: 2,
        name: 'Girls Got Rhythm',
        artist: 'AC/DC',
        album: 'Highway to ■■■■',
        uri: ''
      },
      {
        id: 3,
        name: 'Green river',
        artist: 'Creedence Clearwater Revival',
        album: 'GR',
        uri: ''
      },
      {
        id: 4,
        name: 'Some song',
        artist: 'Good artist',
        album: 'The best album',
        uri: ''
      }],
      playlistName: 'Awesome mix',
      playlistTracks:[{
        id: 5,
        name: 'Rock you like hurricane',
        artist: 'Scorpions',
        album: 'Good album',
        uri: ''
      },
      {
        id: 6,
        name: 'Pretty fly',
        artist: 'The Offspring',
        album: 'Americana',
        uri: ''
      },
      {
        id: 7,
        name: 'By the way',
        artist: 'Red Hot Chili Peppers',
        album: 'Americana',
        uri: ''
      },
      {
        id: 8,
        name: 'Давай вернёмся',
        artist: 'Чайф',
        album: 'Депрессивный альбом',
        uri: ''
      }]
    }
  }
  //Add track to playlist
  addTrack(track) {
    if (this.state.playlistTracks.find(savedTrack => savedTrack.id === track.id)) {
      return;
    } else {
      const currentList = this.state.playlistTracks;
      currentList.push(track)

      this.setState({ //change current state
        playlistTracks: currentList
      })      
    }
  }

  removeTrack(track) {
    const newPlaylistTracks = this.state.playlistTracks.filter(savedTrack => savedTrack.id !== track.id);

    this.setState({
      playlistTracks: newPlaylistTracks
    })
  }

  changePlaylistName(newName) {
    this.setState({
      playlistName: newName
    });
  }

  savePlaylist() {
    const trackURIs = this.state.playlistTracks.map(track => track['uri']);
  }

  search(searchTerm) {
    Spotify.search(searchTerm);    
  }

  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}
                      onNameChange={this.changePlaylistName}
                      onRemove={this.removeTrack}
                      onSave={this.savePlaylist}
                    />
                  </div>
                </div>
              </div>
            );
  }
}

export default App;

Spotify.js

let userAccessToken;
let expiresIn;
const tokenRegEx = /access_token=([^&]*)/; //regular expression to search for user's access token in the URL
const expiresInRegEx = /expires_in=([^&]*)/; //regular expression to search for access token expiration time in the URL
const clientID = '544070faa1314ec4ac27a1ee3eab3049';
const redirectURI = 'http://localhost:3000/';

const Spotify = {
    getAccessToken() {
        if (userAccessToken) {                       
            return userAccessToken;
        } else if (window.location.href.match(tokenRegEx)) {
            userAccessToken = window.location.href.match(tokenRegEx)[1];
            expiresIn = window.location.href.match(expiresInRegEx)[1];
            //wiping out browser's history
            window.setTimeout(() => userAccessToken = '', expiresIn*1000);
            window.history.pushState('Access Token', null, '/');

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

    search(term) {
        this.getAccessToken();
        fetch(`https://api.spotify.com/v1/search?type=track&q=${term}`,
        {
            headers: { 'Authorization': `Bearer ${userAccessToken}` }
        })
        .then(response => response.json())
        .then(response => {
            if (response.tracks.items.length === 0) {
                return []
            } else {                                 
                let tracks = response.tracks.items.map(track => {
                    return {
                        id: track.id,
                        name: track.name,
                        artist: track.artists[0].name,
                        album: track.album,
                        uri: track.uri
                    }
                });
                return tracks;
            }
                        
        })
        .catch(err => console.log(err) );
    }
}

export default Spotify;

On line 44 of Spotify.js you will see this string ‘return tracks’ - this is the result of the promise. How do I pass it to the App.js state?

1 Like

Use .then to change the state after the promise is fulfilled.

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

Also consider calling Spotify.getAccessToken() in app.js instead of inside Spotify.js. Do this immediately after your imports. That way the access token is retrieved once the app loads instead of waiting until the person does a search.

Edit: I had a hard time here as well. It took a bit of trial/error and research to figure out that i could just use .then.

2 Likes

HI! Thanks for your hints but the problem is .then doesn’t work. I get this error:

Uncaught TypeError: Cannot read property ‘then’ of undefined

Hello again.

Have you tried sending Spotify.search(searchTerm) to console in App.js? Is it returning a pending promise?

I think what’s happening is that Spotify.search is returning undefined instead of a pending promise.

If that’s the case then the problem is in Spotify.js. In Spotify.js try replacing return tracks; with console.log(tracks); and see if that displays a pending promise.

I will try your code when I get back in a few hours. We’ll figure this out.

EDIT: Actually, I just noticed that you’re having an issue with scope.

You’re defining tracks inside else but you’re returning it outside of it’s block.

Define tracks as an empty array before your if statement. Inside else just do tracks =

Thanks again! I will try I think this will definitely work because console.log shows ‘undefined’ and thanks to your answer I’m almost 100% sure the problem was in scope.

I also found two solutions I think they might be interesting for you:

Solution #1
In App.js inside .search() method assign this to variable, pass this variable as an argument to Spotify.search(), in Spotify.js in .search() method add another argument like this:

In App.js:

search(term) {
    const rootApp = this;
    Spotify.search(term, rootApp);
}

In Spotify.js:

search(term, app) {
...
app.setState({
 searchResult: tracks
})
}

Solution #2
In App.js .search() invoke Spotify.search() with 2 arguments: term and callback. As a callback define setState…
in Spotify.js add second argument to the .search() method

in App.js:

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

in Spotify.js:

search(term, onSuccess) {
...
.then(response => onSuccess(response))
}

UPD
Tried what you’ve told me but I still get ‘undefined’ when I console.log(Spotify.search()) in App.js

in Spotify.js

        .then (response => {
            let tracks = [];
            if (response.tracks.items.length === 0) {
                return tracks;
            } else {                                 
                tracks = response.tracks.items.map(track => {
                    return {
                        id: track.id,
                        name: track.name,
                        artist: track.artists[0].name,
                        album: track.album.name,
                        uri: track.uri
                    }
                });
            }
            return tracks;                        
        })

The same even if I define variable ‘tracks’ as Spotify object property

Maybe I should create new Promise and return it? Though I have found a solution I’d appreciate your help to understand why it isn’t working as intended

Hello again,

I finally got around to running your code and ran into the same error. Then, I figured out why it’s returning undefined.

You’re not returning your initial fetch promise.

What’s happening right now is this:

  1. Your fetch() promise resolves to your first .then.
  2. Your response.json() promise resolves to your second .then
  3. You manipulate the data and then return the result back to Spotify.search as an array.

At this point, it’s the result of a promise but not a promise itself. That’s why you can’t use .then in app.js but you can do call-backs on the data returned.

Place return in front of your fetch call and then do console.log(Spotify.search) in app.js, you’ll see what I mean.

I hope this helps :slight_smile:

1 Like

Works as indended, thank you!