Hi everyone!
I have done this project before and messed it up on feature request… I started again and now I have 2 issues!
The 1st issue is that i am getting a GET 400 status error when i hit the search button.
The 2nd issue is wheni try to save a playlist i get this error >>>
×
TypeError: util_Spotify__WEBPACK_IMPORTED_MODULE_5_.default.savePlaylist is not a function
I have been stuck on this for some time now and would be grateful if anyone can help??
Here is my code: >>>>>
App.js
import React from 'react';
import '../App/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 = {
//Hardcoded results for this.state.searchResults containing array of track objects
searchResults: [],
playlistName: 'My Playlist',
playlistTracks: [],
};
//The syntax below binds the current value of this to .addTrack
this.addTrack = this.addTrack.bind(this);
//50.a --- The syntax below binds the current value of this to .removeTrack
this.removeTrack = this.removeTrack.bind(this);
//58a. bind this to .updatePlaylistName()
this.updatePlaylistName = this.updatePlaylistName.bind(this);
//64.a bind this to .savePlaylist()
this.savePlaylist = this.savePlaylist.bind(this);
//69.a bind search() to this
this.search = this.search.bind(this);
}
addTrack(track) {
//This let tracks - sets the current state of playlistTracks
let tracks = this.state.playlistTracks;
// This piece of logic determines if the track already exists in playlist and break out of method if it does.
if (tracks.find(savedTrack => savedTrack.id === track.id)) {
return;
} else {
//This pushes the new track to the array
tracks.push(track);
// This sets the new state to playlistTracks (provided the track is not in the playlistTracks)
this.setState({ playlistTracks: tracks })
}
}
// 49. Created a method (removeTrack)
removeTrack(track) {
// 49.b this takes the current value of playlistTracks
let tracks = this.state.playlistTracks;
// 49.c Here we use tracks id to filter out of playlistTracks
tracks = tracks.filter(currentTrack => currentTrack.id !== track.id);
// 49.d Here we set the new value to playlistTracks
this.setState({ playlistTracks: tracks })
}
//57.create a method called updatePlaylistName with the following functionality: Accepts a name argument & Sets the state of the playlist name to the input argument
updatePlaylistName(name) {
this.setState({ playlistName: name });
}
//63. Generates an array of uri values called trackURIs from the playlistTracks property.
savePlaylist() {
const trackUris = this.state.playlistTracks.map(track => track.uri)
Spotify.savePlaylist(this.state.playlistName, trackUris).then(() => {
this.setState({
playlistName: 'New',
playlistTracks: []
})
})
}
search(term) {
Spotify.search(term).then(searchResults => {
this.setState({ searchResults: searchResults })
})
}
render() {
return (
<div>
<h1>Ja<span className="highlight">mmm</span>ing</h1>
<div className="App">
{/*68.b - pass .search() to SearchBar component as an onSearch attribute*/}
<SearchBar
onSearch={this.search} />
<div className="App-playlist">
{/*Pass the state of the App component’s searchResults to the SearchResults component*/}
<SearchResults
searchResults={this.state.searchResults}
//onAdd - passes .addTrack to SearchResults
onAdd={this.addTrack} />
{/*inside of Playlist component we pass down the state of playlistName & playlistTracks*/}
{/* 50.b --- onRemove we pass .removeTrack() to the Playlist component*/}
{/*58b. Pass updatePlaylistName to playlist Component as an attribute onNameChange*/}
{/*64.b - pass savePlaylist to Playlist component*/}
<Playlist playlist={this.state.Playlist}
playlistTracks={this.state.playlistTracks}
onRemove={this.removeTrack}
onNameChange={this.updatePlaylistName}
onSave={this.savePlaylist} />
</div>
</div>
</div>
)
}
}
export default App;
Playlist.js
import React from 'react';
import '../Playlist/Playlist.css';
import TrackList from '../TrackList/TrackList';
class Playlist extends React.Component {
constructor(props) {
super(props);
//60. bind the current value of this to .handleNameChange
this.handleNameChange = this.handleNameChange.bind(this);
}
//59a. Created method (handleNameChange) this method accepts an event that is triggered by an onChange attribute in the Playlist Component input
handleNameChange(event) {
this.props.onNameChange(event.target.value)
}
render() {
return (
<div className="Playlist">
{/*59.b - onChange attribute thats calls the handleNameChange() method*/}
<input
defaultValue={'New Playlist'}
onChange={this.handleNameChange} />
{/*Here we Pass the playlist tracks from the Playlist component to the TrackList component.*/}
{/*51. passing onRemove from playlist component to TrackList component and .removeTrack() with value of true down to TrackList.*/}
<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;```
**SearchBar.js**
import React from ‘react’
import ‘…/SearchBar/SearchBar.css’
class SearchBar extends React.Component {
constructor(props) {
super(props);
//73
this.state = {
term: ''
}
//binded the current value of this to .search()
this.search = this.search.bind(this);
//bind the current value of this to this.handleTermChange
this.handleTermChange = this.handleTermChange.bind(this);
}
// this method passes state of the term to this.props.onSearch
search() {
this.props.onSearch(this.state.term);
}
//71. - this method accepts an event argument and sets the state of the searchbars term to the event targets value
handleTermChange(event) {
this.setState({ term: event.target.value });
}
render() {
return (
<div className="SearchBar">
{/*73. onChange attribute set = to this.handleTermChange*/}
<input placeholder="Enter A Song, Album, or Artist"
onChange={this.handleTermChange} />
<button className="SearchButton" onClick={this.search}>SEARCH</button>
</div>
)
}
}
export default SearchBar;
**SearchResults.js**
import React from ‘react’;
import ‘…/SearchResults/SearchResults.css’;
import TrackList from ‘…/TrackList/TrackList’;
class SearchResults extends React.Component {
render() {
return (
<div className="SearchResults">
<h2>Results</h2>
{/*Pass the search results from the SearchResults component to the TrackList component.*/}
{/*onAdd --- Here we pass onAdd from SearchResults component to TrackList component*/}
{/*isRemoval --- Here we pass isRemoval with value of false to TrackList*/}
<TrackList
tracks={this.props.searchResults}
onAdd={this.props.onAdd}
isRemoval={false} />
</div>
)
}
}
export default SearchResults;
**Track.js**
import React from ‘react’;
import ‘…/Track/Track.css’;
class Track extends React.Component {
constructor(props) {
super(props);
// 46. (this.addTrack) Here we bind this.addTrack to the current value of this
this.addTrack = this.addTrack.bind(this);
// 54. bind removeTrack to current value of this in constructor
this.removeTrack = this.removeTrack.bind(this);
}
//renderAction() = logic to determine if the Track is already added then display button as - else render button as +
renderAction() {
if (this.props.isRemoval) {
return <button className="Track-action" onClick={this.removeTrack}>-</button>
} else {
return <button onClick={this.addTrack} className="Track-action">+</button>
}
}
//45. This addTrack method is created to add (this.props.track) to playlist
addTrack() {
this.props.onAdd(this.props.track);
}
// 53. this method is created in Track component and used to remove (this.props.track from the playlist)
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;
**TrackList.js**
import React from ‘react’;
import ‘…/TrackList/TrackList.css’;
import Track from ‘…/Track/Track’;
class TrackList extends React.Component {
render() {
return (
<div className="TrackList">
{/*When mapping this step out - the app will break until completed up to step 39 - the reason for this is because TrackList is also rendered in SearchResults but with out the {this.props.searchResults}*/}
{/*onAdd --- Here I Pass onAdd from the TrackList component to the Track component. */}
{/*52. Pass onRemove and isRemoval from the TrackList component to the Track component. */}
{this.props.tracks.map(track =>
<Track
track={track}
key={track.id}
onAdd={this.props.onAdd}
onRemove={this.props.onRemove}
isRemoval={this.props.isRemoval} />)}
</div>
)
}
}
export default TrackList;
**Spotify.js**
const clientId = ‘…’;
const redirectUri = ‘http://localhost:3000/’
let accessToken;
const Spotify = {
//78. this method checks if the users token is already set, if it is return the value saved to accessToken
getAccessToken() {
if (accessToken) {
return accessToken;
}
//79. check for access token match
const accessTokenMatch = window.location.href.match(/access_token([^&]*)/);
const expiresInMatch = window.location.href.match(/expires_in=([^&]*)/)
// If the access token and expiration time are in the URL, implement the following steps: Set the access token value / Set a variable for expiration time / Set the access token to expire at the value for expiration time
if (accessTokenMatch && expiresInMatch) {
accessToken = accessTokenMatch[1];
const expiresIn = Number(expiresInMatch[1]);
//This clears the parameters, allowing us 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;
}
},
//86. Inside .search(), start the promise chain by returning a GET request (using fetch()) to the following Spotify endpoint:
//87. Convert the returned response to JSON.Then, map the converted JSON to an array of tracks. If the JSON does not contain any tracks, return an empty array.
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.artists[0].name,
album: track.album.name,
uri: track.uri
}));
});
},
//Create a method in Spotify.js that accepts two arguments. The first argument is the name of the playlist. The second is an array of track URIs. Inside the function, check if there are values saved to the method’s two arguments. If not, return.
savePlayList(name, trackUris) {
if ( !name || !trackUris.length ) {
return;
}
//91. Create three default variables: An access token variable, set to the current user’s access token / A headers variable, set to an object with an Authorization parameter containing the user’s access token in the implicit grant flow request format / An empty variable for the user’s ID
const accessToken = Spotify.getAccessToken();
const headers = { Authorization: `Bearer ${accessToken}` }
let userId;
//92.Make a request that returns the user’s Spotify username. / Convert the response to JSON and save the response id parameter to the user’s ID variable.
return fetch('https://api.spotify.com/v1/me', { headers: headers }
).then(response => response.json()
).then(jsonResponse => {
userId = jsonResponse.id;
// 94. Use the returned user ID to make a POST request that creates a new playlist in the user’s account and returns a playlist ID.
//Use the Spotify playlist endpoints to find a request that creates a new playlist.
//Set the playlist name to the value passed into the method.
//Convert the response to JSON and save the response id parameter to a variable called playlistID.
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;
Thanks in advance for anyone that can help me with these problems!!! :slight_smile: