Jamming - search not working

Hi, I got to about step 95 of the Jamming project and when I click the search button I am not receiving any data from Spotify and no results show up when I search for a song. Could someone look over my code and tell me what I am possibly doing wrong?

Sotify.js

const clientId = '***';
const redirectUri = 'http://localhost:3000';
let accessToken;

const Spotify = {
    getAccessToken() {
        if (accessToken) {
            return accessToken;
        }

        // check for an access token match
        const accessToeknMatch = window.location.href.match(/access_token=([^&]*)/);
        const expiresInMatch = window.location.href.match(/expires_in=([^&]*)/);

        if (accessToeknMatch && expiresInMatch) {
            accessToken = accessToeknMatch[1];
            const expiresIn = Number(expiresInMatch[1]);
            // This clears the parameters, allowing to grab a new access token when it expires
            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();
        return fetch(`https://api.spotify.com/v1/search?type=track&q=${term}`, {
            headers: {
                Authorization: `Bearer ${accessToken}`
            }
        }).then(response => {
            return response.json();
        }).then(jsonResponse => {
            if (!jsonResponse.tracks) {
                return [];
            }
            return jsonResponse.tracks.items.map(track => ({
                id: track.id,
                name: track.name,
                artist: track.artist[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 => {
                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 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.state = {
      searchResults: [],
      playlistName: 'My Playlist',
      playlistTracks: [] 
    }

    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);
  }

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

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

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

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

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

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


export default App;

SearchBar.js

import React from "react";

import './SearchBar.css';

class SearchBar extends React.Component {

    constructor(props) {
        super(props);

        this.stae = {
            term: ''
        }

        this.search = this.search.bind(this);
        this.handleTermChange = this.handleTermChange.bind(this);
    }

    search() {
        this.props.onSearch(this.state.term);
    }

    handleTermChange(e) {
        this.setState({ term: e.target.value });
    }

    render() {
        return (
        <div className="SearchBar">
            <input onChange={this.handleTermChange} placeholder="Enter A Song, Album, or Artist" />
            <button onClick={this.search} className="SearchButton">SEARCH</button>
        </div>
        )
    }
}

export default SearchBar;

Playlist.js

import React from "react";

import './Playlist.css';

import Tracklist from "../Tracklist/Tracklist";

class Playlist extends React.Component {

    constructor(props) {
        super(props);

        this.handleNameChange = this.handleNameChange.bind(this);
    }

    handleNameChange(e) {
        this.props.onNameChange(e.target.value);
    }

    render() {
        return (
            <div className="Playlist">
                <input defaultValue={"New Playlist"} onChange={this.handleNameChange}/>
                <Tracklist tracks={this.props.playlistTracks}
                           onRemove={this.props.onRemove}
                           isRemoval={true} />
                <button className="Playlist-save" onClick={this.props.onSave}>SAVE TO SPOTIFY</button>
            </div>
        )
    }
}

export default Playlist;

Tracklist.js

import React from "react";

import './Tracklist.css';
import Track from "../Track/Track";

class Tracklist extends React.Component {
    render() {
        return (
            <div className="TrackList">
                {
                     this.props.tracks.map(track => {
                        return <Track track={track}
                                      key={track.id}
                                      onAdd={this.props.onAdd}
                                      onRemove={this.props.onRemove}
                                      isRemoval={this.props.isRemoval} />
                    })
                }
            </div>
        )
    }
}

export default Tracklist;

Track.js

import React from "react";

import './Track.css';

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


        this.addTrack = this.addTrack.bind(this);
        this.removeTrack = this.removeTrack.bind(this);
    }

   renderAction() {
        if (this.props.isRemoval) {
            return <button className="Track-action" onClick={this.removeTrack}>-</button>
        } else {
            return <button className="Track-action" onClick={this.addTrack}>+</button>
        }
    }

    addTrack() {
        this.props.onAdd(this.props.track);
    }

    removeTrack() {
       this.props.onRemove(this.props.track); 
    }

    render() {
        return (
            <div className="Track">
                <div className="Track-information">
                    <h3>{this.props.track.name}</h3>
                    <p>{this.props.track.artist} | {this.props.track.album}</p>
                </div>
               {this.renderAction()}
            </div>
        )
    }
}

export default Track;

SearchResults.js

import React from "react";

import './SearchResults.css';

import Tracklist from "../Tracklist/Tracklist";

class SearchResults extends React.Component {
    render() {
        return (
            <div className="SearchResults">
                <h2>Results</h2>
                <Tracklist tracks={this.props.searchResults}
                           onAdd={this.props.onAdd}
                           isRemoval={false} />
            </div>
        )
    }
}

export default SearchResults;

have the same problem. I followed the ā€œGet Unstuckā€ instructions code by code but it still didnā€™t work. You can try to put onClick on one of the components as suggested by the Youtube comments, but it still didnā€™t work for me. Skipped the project to continue with the course and put Jammming on hold as it wasted a lot of time to debug. Sometimes the answer might not be given to you immediately, so try to continue the course and who knows, the answer my be taught in the newer courses! Cheers :wink:

In the below code, the artist property is actually found at track.artists[0].name

Once you fix that I believe you should find get a result.

In your search bar component it appears a typo may have gotten in there, with this.stae instead of this.state! Not totally sure if this is necessarily the reason for the issue, but given the SearchBar state property term is the one used for the search itself itā€™s possibly that!

1 Like

Hey guys! Any luck on figuring this out?? Iā€™m facing the same issue after step 95.

Screenshot below:

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.state = {
      searchResults: [],
      playlistName: 'My Playlist',
      playlistTracks: []
    };
    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);
  }

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

  removeTrack(track) {
    let tracks= this.state.playlistTracks;
    tracks = tracks.filter(currentTrack => currentTrack.id !== track.id);
    this.setState({playlistTracks: tracks});
  }

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

  savePlaylist() {
    //alert("this method is linked to the button");
    const trackURIs = this.state.playlistTracks.map(track => track.uri);
    Spotify.savePlaylist(this.state.playlistName, trackURIs).then(() => {
      this.setState(
        {
          playlistName: 'New Playlist',
          playlistTracks: []
        }
      )
    })
  }

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

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

export default App;

Spotify.js

//import React from "react";
import SearchBar from "../Components/SearchBar/SearchBar";

const clientID= '46f90462bc1347eaa9b049502a9734c9';
const redirectURI= 'http://localhost:3000/';
let accessToken;


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]);
        //this clears the parameters, allowing us to grab a new access token when old expires
        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();
        return fetch(`https://api.spotify.com/v1/search?type=track&q=${term}`, {
            headers: {
                Authorization: `Bearer ${accessToken}`
            }
        }).then(response => {
            return response.json();
        }).then(jsonResponse => {
            if(!jsonResponse.tracks) {
                return [];
            }
            return jsonResponse.tracks.items.map(track => ({
                id: track.id,
                name: track.name,
                artist: track.artist[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 => {
                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;

SearchBar.js

import React from "react";
import './SearchBar.css';

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

    this.state = {
      term: ''
    }

    this.search = this.search.bind(this);
    this.handleTermChange = this.handleTermChange.bind(this);
    
  }

  search() {
    this.props.onSearch(this.state.term);
  }

  handleTermChange(event) {
    this.setState({term: event.target.value});
  }

  render() {
    return (
      <div className="SearchBar">
        <input placeholder="Enter A Song, Album, or Artist" onChange={this.handleTermChange} />
        <button className="SearchButton">SEARCH</button>
      </div>
    );
  }
}

export default SearchBar;

SearchResults.js

import React from "react";
import './SearchResults.css';
import TrackList from "../TrackList/TrackList";

class SearchResults extends React.Component {
  state = {};
  render() {
    return (
        <div className="SearchResults">
        <h2>Results</h2>
        <TrackList tracks={this.props.searchResults} 
                    onAdd={this.props.onAdd}
                    isRemoval={false}/>
      </div>
    );
  }
}

export default SearchResults;

This should be

artist: track.artists[0].name,

I made this same mistake and see it often on this project. I havenā€™t dug deeper into your code, but start with this and hopefully it gets you rolling again.

1 Like

thanks so much, i literally figured this out 5mins ago. Appreciate the help.

However, now that this is fixed I realise my savePlaylist has issues. Getting the below error. Can you pls take a look and suggest?

App.js:51 Uncaught TypeError: Cannot read properties of undefined (reading ā€˜thenā€™)
at App.savePlaylist (App.js:51:1)

You need to return the converted response in the savePlaylist method.

.then((response) => {
            return response.json();  // you need to return this response
          })
          .then((jsonResponse) => {
            const playlistId = jsonResponse.id;
            return fetch(