Jammming (front-end development capstone project) - my own take

Hi there,

After completing Jammming project for the first time I was not completely happy with it. I felt I wasn’t fluent in React and wished there was another project to work on. Then a pretty big Codecademy update happened (which included several smaller projects) and I decided to do Jammming again from scratch.

My second go meant copying the presentational components (I altered them to some extent later) but working on developing logic independently (this led to states and props names occasionally different from original ones). It was extremely rewarding so I decided to take the challenge further and implement almost all extra features listed in the second part of the project plus some made up ones.

The project is available here:

The code is available here:

All major new functions and changes are listed in the readme.txt file.

Any feedback, comments and questions, are welcome and appreciated! In particular, I would be super happy with any hints how to make parts of the code (most of the logic in App.js and Spotify.js) less repetitive.

Happy coding!

Pyllo

2 Likes

Very impressive!

Could you explain how you solved the extra feature that prevents the page from refreshing when performing a search? (no. 2 in your readme file). Specifically, how did you keep the search term after getting the access token. and then automatically execute the search after getting the access token?

1 Like

Thanks! :relaxed:

Firstly, I tweaked Spotify.getAccessToken() method by setting real expiry time using Date.now():

const tokenExpiryDate = Date.now() + (this.tokenExpiry * 1000);
setTimeout(() => (this.accessToken = ''), tokenExpiryDate - Date.now());

Then, I googled up the issue of losing React app data on browser refreshing and encountered a localStorage JS property in a number of Stack Overflow posts. In short it allows to save data in browser memory (rather than app memory) so it can be read by the app after it refreshes following redirections to Spotify api during obtaining the access token. The page does refresh - but the data are saved in sessionStorage (a variant of localStorage I chose for personal preference) and the <App>'s state is updated with saved data after refreshing. I use this in all functinalities where there is a chancethat the app would have to get an access token, for example:

search() {
    Spotify.generateURLState();
    sessionStorage.searchPhrase = this.state.searchPhrase; //searchPhrase stored locally
    sessionStorage.playlistName = this.state.playlistName; // playlistName stored locally
    sessionStorage.playlistTracks = JSON.stringify(this.state.playlistTracks); // playlistTrack array stringified (localStorage and sessionStorage only supporting strings) and stored locally
    const needsLogging = Spotify.spotifyLogin(sessionStorage.urlState);
    if (!needsLogging && this.state.searchPhrase) {
      Spotify.search(this.state.searchPhrase).then(response => this.handleSearchResponse(response));
      this.getPlaylists();
    } else if (!needsLogging && !this.state.searchPhrase) {
      this.getPlaylists();
    }
  }

In the above example needsLogging returns true only if access token needs to be obtained and redirection to Spotify api takes place which deletes all app data not stored locally. Once the redirection back to Jammming with access token in theURL after hash, componentDidMount() method recognises this and sets state from values stored locally (I removed irrelevant code for clarity):

 componentDidMount() {
    if (window.location.href.match(/access_token=/))  { // recognition that the redirection back from Spotify just happened
      Spotify.getAccessToken(); // extracts access token from hash part of URL and clears URL displayed
      this.setState({
        searchPhrase: sessionStorage.searchPhrase, // searchPhrase restored from local storage
        searchTracks: ((!sessionStorage.searchPhrase && sessionStorage.searchTracks) ? JSON.parse(sessionStorage.searchTracks) : []), // searchTracks if has been stored gets parsed back to an array
        playlistName: sessionStorage.playlistName, // playlistName restored from local storage
        /*playlistTracks: JSON.parse(sessionStorage.playlistTracks) */
      }, () => { // second parameter in setState method is a callback function that will be executed after setState is completed and the component is re-rendered
        if (this.state.searchPhrase) { // I instruct the app to perform and handle search automatically 
          Spotify.search(this.state.searchPhrase).then(response => this.handleSearchResponse(response));
        };    
      )};
    }
  }

I worked the above out after some research and by trial and error - by all means I am not a professional programmer - but I hope this makes sense! If one of the moderators stumbles across this post at some point I hope she or he will correct me wherever needed…

Thanks again for taking a look and if you have any more questions feel free to ask :slight_smile:

Happy coding!

Pyllo

1 Like

Thanks so much for the detailed response mate!

I too had discovered localStorage/sessionStorage after a bit of research, I just wasn’t sure how to implement it in my code or if it was even right for this problem. So thanks for helping me out!

In the end, I stuck with the original code from the project instructions more or less. I have left the getAccessToken() method as it is for now. I haven’t added the user playlists feature either, so I just applied the sessionStorage to the user search function, with surprisingly few lines of code.

Seems to work as planned! Though if there’s something critical or particularly useful that I’m missing, do tell!

App.js

search(term) {
  window.sessionStorage.searchTerm = term;
  // sessionStorage, unlike localStorage, won't display search results until AFTER user logs in

  Spotify.search(term).then(searchResults => {
    this.setState({searchResults: searchResults})
  })
  // here we want to update the state of SearchResults with the value resolved from our Spotify.search promise
  // we update the searchResults state with the searchResults as returned from our promise
}
componentDidMount() {
    if (window.location.href.match(/access_token=/)) {
      Spotify.search(window.sessionStorage.searchTerm).then(searchResults => {
        this.setState({searchResults: searchResults});
      })
    }
  }

One last question if you have a moment, could you explain the purpose of your Spotify.generateURLState() method and the reason behind assigning it a random number? I understand it has something to do with the optional ‘state’ query parameter when requesting app authorisation.

Thanks again for your help before!

1 Like

My pleasure! Glad to hear it helped to solve the issue on your side. Your work looks good, and you made a good observation that sessionStorage will be empty in the beginning. BTW in my project on a few occasions I gave the locallly stored variables the value of en empty string (in the the setState callback second argument to have better control what exactly is stored locally when the app got more complex - maybe you’ll find this useful.

You’re right, this is my take to implement this “strongly recommended” Spotify authorization feature. Basically, Spotify.generateURLState() method generates a random number which is then saved in local memory and attached to the query string. When Spotify redirects back after with hashed access token, the state is also included and it should be identical to generated state. The app then checks if the value received in the hashed URL and the value stored locally are equal and only extracts the access token if this condition is met. This is, according to these docs, one of the methods to ensure that data received in the hashed URL are from the trusted (Spotify) source and therefore not misleading/malicious. This is a random number to prevent a potential attacker from guessing the URLState by reading app’s source code.

Again, it’s my pleasure to exchange opinions and share coding experiences here, feel free to write anytime.

Pyllo

Legend. Thanks again for clarifying all that! There’s so much to learn and I rely on people like you for your generous input.

1 Like