Jammming 400 error (Only valid bearer authentication supported)

https://www.codecademy.com/paths/web-development/tracks/front-end-applications-with-react/modules/jammming/projects/jammming-prj

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:

The formatting of your code needs to be fixed for the forums. The first couple of them are done well, but then something goes wrong and it makes it a mess to review.

I can tell you from issue #2 that your
TypeError: util_Spotify__WEBPACK_IMPORTED_MODULE_5_.default.savePlaylist is not a function error
is because you named the method savePlayList in Spotify.js but you try to call it with savePlaylist. JavaScript is case-sensitive so that’s a different method name.

1 Like

Hey i didnt realise it had messed the formatting up!! However your advice on savePlaylist was the solution to both issues!! I am now getting search results!!!

Thanks again :slight_smile:

That worked out perfectly then :laughing:

1 Like

ok so now i search eminem and i get results anything else no results and bad request again!!!??? I will reupload my code if you dont mind having a look for me??

Thanks again

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;

Spotify.js

const clientId = '47a22193a4e348c1901b7eec9a0518dd';

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;

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;

Thanks again!!

for some reason the savePlaylist is not working again!!..

It looks like your regex for the token isn’t entirely correct and is likely producing some odd results, perhaps impacting subsequent searches and saving the playlist too. This part was given directly in the project instructions with a note about it being beyond the scope of the project, but maybe you typed it rather than copy/pasting:

window.location.href.match(/access_token=([^&]*)/);
1 Like

You are a legend!! I just copy/paste and now its working perfectly!!! :slight_smile: Now i can finish up and move onto the feature request :wink:

Thanks again for your support!

You’re welcome and congrats on finishing the project! :tada:

1 Like