Jammming project: Spotify API returns cors-anywhere response

I am stuck on an error near the end of the Jammming project.

My response from the Spotify API comes back as this when console logged:

It is a cors-anywhere response instead of Spotify data for the search. Looks like it is not redirecting from cors-anywhere proxy? This shows up like this whether or not I have the cors-anywhere prefix in the request URL. Below is my code:

Spotify.js:

let accessToken;
const clientID = '39e5a52079bd485f8dab3d4c0556de73';
const redirectURI = "http://localhost:3000/";

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]);
      // clear parameters and allow us to acces  new access access_token
      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();
    console.log(accessToken)

    return fetch(`https://api.spotify.com/v1/search?type=track&q=${term}`, 
    { headers: {
        Authorization: `Bearer ${accessToken}`,
        'Content-Type': 'application/json' 
        // 'Access-Control-Allow-Header': 'http://localhost:3000',
        // Origin: 'https://localhost:3000'
      }
    })

    // var myHeaders = new Headers();
    // myHeaders.append("Authorization", "Bearer BQB2p2uHUv19Oi9RZQravwPmPzuKsH7wyeP3ZS5Z3dqzBBcSEqEdiEtLBk4mZmA3LUeG78ETFEjkE9eqwNrNb0rjrmbPJxnx-5SWmLEru-FFt3TiY9A-uL1NJa78LRiHeoa_TD3E9XfA8wBt_nraYecl_A8WAGbCCV41KhkHSQriOpdZDH4H");
    
    // var requestOptions = {
    //   method: 'GET',
    //   headers: myHeaders,
    //   redirect: 'follow'
    // };
    
    // fetch(`https://api.spotify.com/v1/search?type=track&q=${term}`, requestOptions)
    //   .then(response => response.text())
    // //   .then(result => console.log(result))
      
    .then(jsonResponse => {
        console.log(jsonResponse)
        if(!jsonResponse.track) {
            return [];
        } else {
            return jsonResponse.tracks.items.map(track => ({
                id: track.id,
                name: track.name,
                artist: track.artists[0].name,
                uri: track.uri
            }))
        }
    })
    .catch(error => console.log('error', error))
  },

  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 => {
              console.log(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;

App.js

import React, { Component } 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 Component {
  constructor(props) {
    super(props)

    this.state = {
      playlistName: 'playlist name',
      playlistTracks: [],
      searchResults: []
    }

    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)

  }

  removeTrack(track) {
    let newList = this.state.playlistTracks.filter(track.id)
    this.setState({ playlistTracks: newList })
  }

  addTrack(track) {
    if(this.state.playlistTracks.find(savedTrack => 
      savedTrack.id === track.id)) {
        return;
    }
  }

  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: []
      })
    })
  }

  // componentDidMount() {
  //   window.addEventListener('load', () => {Spotify.getAccessToken()});
  // }

  search(term) {
    console.log(term)
    Spotify.search(term).then(searchResults => {
      this.setState({ searchResults: searchResults })
    })
  }

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

export default App;

Your response will come from CORS Anywhere, because that’s what you’re fetching. It’s a proxy, so what you’re effectively doing is asking CORS Anywhere to be the “middle man” in your transaction. You’re not going to get a response directly from Spotify when your fetch() is going somewhere else. :slight_smile:

Are you actually getting an error somewhere further along the chain, or can you chain that response into the appropriate .then() to get the JSON object to manipulate?

I don’t remember this project using cors-anywhere so you should not be getting a response from there. I believe the response should come from the Spotify server. The Spotify Implicit Grant flow was a little tricky to get working but I would start by troubleshooting the response from cors-anywhere when that is not where the fetch is going.

Yeah… the posted code is going direct to Spotify, but the screenshot of the response clearly indicates that it’s coming from CORS Anywhere…

Something doesn’t match up here. :slight_smile:

In addition to the output you posted not matching up with the code you posted that has been mentioned, I’m also not seeing where you’re chaining the .json() that reads the response stream to completion, which also returns a Promise.

Usually the chain includes: fetch() -> response.json() -> processing of json

I only see the ‘fetch’ and ‘processing of json’ in the current version of the code you posted. In the code comments, the closest thing that came to the missing part of the chain was .then(response => response.text())

Edit: To be clear, I’m talking about your search() function in Spotify.js. Your savePlaylist() function has the json() part of the chain.

1 Like

I get a response as undefined when I include the response.json(), the error is saying that response.json() is not a function, then the Spotify.search(term) back in App.js returns undefined. I am now able to console.log data using axios instead, but whether or not I response.json() or use response.text(), App.js gets undefined, not the spotify response.

Using axios got me past the cors anywhere response, thank god. Here is my code now
In Spotify

search(term) {
    // const accessToken = Spotify.getAccessToken();
    // console.log(accessToken)

    var config = {
      method: 'get',
      url: `https://api.spotify.com/v1/search?type=track&q=${term}`,
      headers: { 
        'Authorization': 'Bearer BQB2p2uHUv19Oi9RZQravwPmPzuKsH7wyeP3ZS5Z3dqzBBcSEqEdiEtLBk4mZmA3LUeG78ETFEjkE9eqwNrNb0rjrmbPJxnx-5SWmLEru-FFt3TiY9A-uL1NJa78LRiHeoa_TD3E9XfA8wBt_nraYecl_A8WAGbCCV41KhkHSQriOpdZDH4H'
      }
    };
    
    return axios(config)
    .then(function (response) {
      console.log(JSON.stringify(response.data));
    })
    .then(response => {
        response.json()
      })
    .catch(function (error) {
      console.log(error);
    });
}

and my response is:

If you use Axios (which I like) then the json conversion is done by axios already. You should not use .json in this case. The data will be in response.data. But it may still be easiest to stick with fetch for this project since the walkthrough is using fetch.

I agree with @mike7127143742 - I also like Axios and think that sticking to fetch for this project is probably best until you’re comfortable enough to get away from the walkthrough.

Even if Axios did have a .json() method, then the code in your last post still wouldn’t work because response would always be undefined since you don’t return anything in the previous chain.

I plugged in your Spotify search() function from your original post into my project, added the .json() part of the chain, corrected a small mistake in the if condition that evaluates the json in the next chain, and was able to get results:
image
(ps. album name is missing because you didn’t include it in the object you create when processing the json)

Based on what I’ve seen in your code so far, I think I know where some of the confusion is coming from. The implicit return values from arrow functions don’t work the way you sometimes use them and it is leading to bugs that you may be attributing to other things.

.then(response => {
  response.json();
}) 


.then(response => response.json())  

These are very different. The first one isn’t actually returning a value, so the next part of the chain starts working with undefined. The 2nd one is implicitly returning the value of response.json() so the next part of the chain works with the results.

This would also work because the value of response.json() is explicitly returned:

.then(response => {
  return response.json();
})

I’m mentioning this because of the code you posted most recently, but also because I see you use it incorrectly in your savePlaylist() function code in your original post, so it will eventually come up again.

As for the other mistake I mentioned, your if (!jsonResponse.track) code will force it to return an empty array every time. It should be checking for jsonResponse.tracks since that’s the property that Spotify sends.

3 Likes

If you use fetch, just make sure to use a supported browser version as the overall browser support is better with Axios. I don’t think Internet Explorer is supporting fetch for example. If you use a recent chrome version then you are good with fetch. Just another consideration if having issue with the requests.

Thank you for the reply, I didn’t realize I was using the arrow functions wrong there.

I’m still stuck here, though. I am now getting data from Spotify with fetch but I am still running into an error when it goes back to App.js, and the return of Spotify.search(term) is for some reason still undefined. I am able to console.log jsonResponse.tracks.items in Spotify.js, and then I followed the guide exactly for the .map, but it’s still undefined when sent back to App.js I think I have gone insane or something.

Here is the error in the console form App.js:

Here is my Spotify.js:

let accessToken;
const clientID = "39e5a52079bd485f8dab3d4c0556de73";
const redirectURI = "http://localhost:3000/";
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]);
      // clear parameters and allow us to acces  new access access_token
      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();

    fetch(`https://api.spotify.com/v1/search?type=track&q=${term}`, {
      headers: {
        Authorization: `Bearer ${accessToken}`,
        "Content-Type": "application/json",
      },
    })
      .then(response => response.json())
      .then((jsonResponse) => {
        console.log(jsonResponse);
        if (!jsonResponse.tracks) {
          return [];
        } else {
          console.log(jsonResponse.tracks.items);
          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) => {
            console.log(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;

Here is my App.js:

import React, { Component } 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 Component {
  constructor(props) {
    super(props)

    this.state = {
      playlistName: 'playlist name',
      playlistTracks: [],
      searchResults: []
    }

    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)

  }

  removeTrack(track) {
    let newList = this.state.playlistTracks.filter(track.id)
    this.setState({ playlistTracks: newList })
  }

  addTrack(track) {
    if(this.state.playlistTracks.find(savedTrack => 
      savedTrack.id === track.id)) {
        return;
    }
  }

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

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

  search(term) {
    Spotify.search(term)
    .then(function(searchResults) {
      this.setState({ searchResults: searchResults })
    })
  }

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

export default App;

Thanks, I am using Chrome so that shouldn’t be the issue.

You’ve created a couple new issues in all the refactoring:

  1. Spotify.js search() function is no longer returning a value at all, so it will always evaluate to undefined. In your original post, you correctly had a return keyword before your call to fetch()

That’s the cause of the immediate TypeError you posted.

The other new issue you’re going to run into after you fix the issue I mentioned above is going to be new to you, so I’ll let you work through that. Click below if you need some hints about how to resolve it.

More info for the 2nd issue if you need it
  1. Your refactored App.js search() function will cause an error because it will not be able to access the same this that setState() is on due to you switching it to an anonymous function instead of an arrow function.

The version you had in your original post didn’t need to be changed.

1 Like