0

So I have this button:

<button onClick={this.setRecommendations}>
   log into Spotify
</button>

Calls this function:

setRecommendations(){
    recommendations().then(recs => {
      this.setState({recommendations: recs});
    });
  }

Which calls this function:

export async function recommendations(){
  const unique = await findUnique();
  const recommendations = [];
  for(var index = 0; index < unique.length; index++){
    var trackURI = (unique[index].uri).slice(14, (unique[index].uri).length);
    var rec = await Spotify.recommendations(trackURI, unique[index].pop);
    for(var i=0; i<rec.length; i++){
      recommendations.push(rec[i]);
    }
  }
  const uniqueRec = getUnique(recommendations);
  return await uniqueRec;
}

Which calls another function and another function ultimately one of the first things it does is call getAccessToken:

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=${scopes.join("%20")}&redirect_uri=${redirectUri}`;
      window.location = accessUrl;
    }
  },

The functions ultimately work but you have to click the button twice. Last night I tried putting some console.log()s to see what was happening and then I realized that this.setRecommendation doesn't seem like it is being called until the second click. but the button still took you to the login screen so somehow it was getting to getAccessToken:

I even tried:

setRecommendations(){
    console.log("you pushed the button");
    recommendations().then(recs => {
      this.setState({recommendations: recs});
    });
  }

And "you pushed the button" would still not be console logged until the second click, but again it would take you to the login, so I created a second function login() an all it does is call getAccessToken():

  login(){
    Spotify.getAccessToken();
  }

So I have two buttons, one calls this.login the other calls this.setRecommendations when I click this.login and then this.setRecommendations it works as you expected, populates the components nicely.

But I would still like this to be one click. I tried:

  <button onClick={() => {
                          this.login();
                          this.setRecommendations();}}>
          log into Spotify
          </button>

But that doesn't work it still calls this.login() and doesn't seem to call this.setRecommendations() until once again the second click.

this is my App Component

import React from 'react';
import './App.css';
import {Spotify, recommendations} from '../../utils/Spotify';
import RecommendationButton from '../RecommendationButton/RecommendationButton';
import Playlist from '../Playlist/Playlist';
import Graphs from '../Graphs/Graphs'
import RecommendationResults from '../RecommendationResults/RecommendationResults';


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

    this.state = {
      searchResults: [],
      recommendations: [],
      playlistName: 'New Playlist',
      playlistTracks: [],
      topAlbums: ["Cats", "Wicked", "Heathers", "Charli", "Little Mermaind"],
      album_count: [10, 20, 25, 30, 35],
      topArtist: ["Dua Lipa", "Sierra Boggess", "Barrett Wilbert Reed", "Charli XCX", "Jessica Klean"],
      artist_count: [5, 10, 25, 35, 55],
      topGenre: ["classical", "broadway", "pop", "punk", "hip-hop"],
      genre_count: [50, 25, 5, 13, 7],
      popRange: ["0-20", "21-40", "41-60", "61-80", "81-100"],
      pop_count: [20, 40, 60, 40, 20]
    };
    this.search = this.search.bind(this);
    this.login = this.login.bind(this);
    this.setRecommendations = this.setRecommendations.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});
    });
  }

  login(){
    Spotify.getAccessToken();
  }

  setRecommendations(){
    recommendations().then(recs => {
      console.log(recs);
      this.setState({recommendations: recs});
    });
  }

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

    tracks.push(track);
    this.setState({playlistTracks: tracks});
  }

  //removeTracks
  removeTrack(track) {
    let tracks = this.state.playlistTracks;
    tracks = tracks.filter(currentTrack => currentTrack.id !== track.id);

    this.setState({playlistTracks: tracks});
  }

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

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

  //This what we will see
  render() {
    return (
      <div>
        <h1>Spotify Recommendations</h1>
        <div className="App">
          <button onClick={this.login}>
          log into Spotify
          </button>
          <RecommendationButton onPush={this.setRecommendations} />
          <Graphs data={this.state.album_count} margins={this.state.topAlbums} graphID={"topAlbums"} />
          <div className="Graphs">
            <Graphs data={this.state.artist_count} margins={this.state.topArtist} graphID={"topArtist"}/>
          </div>
          <p> below are some recommendations based on your listening history </p>
          <div className="App-playlist">
            <RecommendationResults recommendationResults={this.state.recommendations}
                           onAdd={this.addTrack} />

            <Playlist playlistName={this.state.playlistName}
                      playlistTracks={this.state.playlistTracks}
                      onNameChange={this.updatePlaylistName}
                      onRemove={this.removeTrack}
                      onSave={this.savePlaylist} />
          </div>
        </div>
      </div>
    );
  }
}


export default App;
5
  • Can you post the whole component, including the render function, please? Commented Dec 29, 2019 at 15:39
  • 1
    Did you bind the setRecommendations function? Ex: setRecommendations = () => {} Commented Dec 29, 2019 at 15:41
  • setRecommendation is binded, I am updating to include more code. Commented Dec 29, 2019 at 17:05
  • I can tell you exactly what is wrong now. The foundation for this was a codecademy project that is purely client side 100% in React and JavaScript. Whatever they are doing in getAccessToken must be done first. Before anything requiring an Access Token can be done. You may see in the else clause of the function nothing is done after window.location=[the url] thus if a function calls getAccessToken, if it is the first time they will not receive anything, it won't be until the second time it is called that a token will be returned. Now if you have suggestions for that let me know. Commented Dec 29, 2019 at 18:18
  • Does this answer your question? I have to click the button twice to get states to render correctly in React Commented Dec 29, 2019 at 21:49

1 Answer 1

1

Do you need the Access Token before you are able to get the recommendations?

It may be the case that on the first click the program does not yet have the access token needed to get the recommendations and that's why it takes two clicks, because there is a pause for it to get the access token between the two clicks. This may be caused by the following line in getAccessToken:

window.setTimeout(() => accessToken = '', expiresIn * 1000);

You could try having getAccessToken() return a promise with the resolve value being the access token. And then in your login function call getAccessToken and .then() setRecommendations.

In your JSX you would just have onClick={this.login}.

Sign up to request clarification or add additional context in comments.

3 Comments

I can tell you exactly what is wrong now. The foundation for this was a codecademy project that is purely client side 100% in React and JavaScript. Whatever they are doing in getAccessToken must be done first. Before anything requiring an Access Token can be done. You may see in the else clause of the function nothing is done after window.location=[the url] thus if a function calls getAccessToken, if it is the first time they will not receive anything, it won't be until the second time it is called that a token will be returned. Now if you have suggestions for that let me know.
You could just call getAccessToken() at the end of the else block in getAccessToken after you set the new window location
Yes, that could work, so simple, yet evaded me this whole time.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.