Jammming, React Project: Warning: Each child in a list should have a unique "key" prop

Hello everyone,

I am currently working on the Jammming project in the React section. Here is the link to the project. Here is the link to the project: Jammming Project

I’ve finished all the steps. However I’m getting this warning:

It says that "Each child in a list should have a unique “key” prop. It also tells me to “Check the render method of ‘Tracklist’”.
I’m not sure why I’m getting this warning because I’ve included a key prop in the render method of ‘Tracklist’. Here is my Tracklist.js code :

import React from 'react';
import './Tracklist.css';
import {Track} from '../Track/Track.js';

export class Tracklist extends React.Component {
  
    render() {
        return (
    <div className="TrackList">
    {/*<!-- You will add a map method that renders a set of Track components  -->*/}
            {this.props.tracks.map(track =>{
                return <Track track={track} key={track.id} 
                    onAdd={this.props.onAdd} 
                    isRemoval={this.props.isRemoval} 
                    onRemove={this.props.removeTrack}
                    />
                })
            }
            
    </div>
        );
    }
}

I also tried hardcoding the SearchResults. I included a name, artist, album, and id value and I don’t get the key warning. I think it may have something to do when I pass the values from my Spotify Fetch request. Perhaps my app is not saving the id value into the key. Here is my
Spotify.js code:

et accessToken;
const clientID='';
const redirectURI='http://localhost:3000/';

const Spotify = {
    getAccessToken(){
        const accessTokenMatch = window.location.href.match(/access_token=([^&]*)/);
        const expiresInMatch = window.location.href.match(/expires_in=([^&]*)/);

        if(accessToken){
            return accessToken;
        }
        else if(accessTokenMatch 
                && expiresInMatch ) {
            accessToken = accessTokenMatch[1];
            
            let expiresIn = Number(expiresInMatch[1]); 
            window.setTimeout(() => accessToken = '', expiresIn * 1000);
            window.history.pushState('Access Token', null, '/');
            return accessToken;
                }

        else if(!accessToken && !window.location.href.match(/access_token=([^&]*)/)){
            window.location = `https://accounts.spotify.com/authorize?client_id=${clientID}&response_type=token&scope=playlist-modify-public&redirect_uri=${redirectURI}`;
        }
    },
    search(searchTerm){
        const passedAccessToken = Spotify.getAccessToken();
        console.log('This is the passed access token:' + passedAccessToken);
        console.log('this is the search term: ' + searchTerm)
        const endpoint = `https://api.spotify.com/v1/search?type=track&q=${searchTerm}`;
        const header ={Authorization: `Bearer ${passedAccessToken}`};
        console.log(header);

        return fetch(endpoint,
            {headers: header}
            )
            .then(response=>{
                if(response.ok){
                return response.json();
                    }
                throw new Error('Request Failed!');},
                (networkError)=>{
                    console.log(networkError.message);
                })
            .then(jsonResponse => {
                if(!jsonResponse){
                    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}
                        ]);
            });
    },
    savePlaylist(playlistName,trackURIArrays){
        if(!playlistName && !trackURIArrays)
            return [];
        
        let usersAccessToken = accessToken;
        const header = {
            'Authorization':`Bearer ${usersAccessToken}` //not entirely sure
        };
        let userId='';
        const urlToFetch= 'https://api.spotify.com/v1/me';

        fetch(urlToFetch,header)
            .then((response)=>{
                if(response.ok){
                    return response.json();
                }
                throw new Error('Request Failed!');},
                (networkError)=>{
                    console.log(networkError.message);
                })
            .then(jsonResponse=>{
                userId=jsonResponse.id;
                return userId;
                });

            const urlForPost = `https://api.spotify.com/v1/users/${userId}/playlists`; 
            const name = playlistName;
        
        fetch(urlForPost,{
            method:'POST',
            headers: {
                'Content-type': 'application/json'
                },
            body:name
            })
            .then(response =>{
                if(response.ok){
                    return response.json();
                }
                throw new Error('Request failed!');
            },
            networkError => console.log(networkError.message)
            )
            .then(jsonResponse=>{
                const playlistID = jsonResponse.id;
                return playlistID;
            })

        

    }
}
export default Spotify;

And here is my app.js code:

import './App.css';
import React from 'react';
import {SearchBar} from '../SearchBar/SearchBar.js';
import {SearchResults} from '../SearchResults/SearchResults.js';
import {Playlist} from '../Playlist/Playlist.js';
import Spotify from '../../util/Spotify.js';

export class App extends React.Component{
  constructor(props){
    super(props);
    this.state = {searchResults:[],
                  playlistName:'',
                  playlistTracks:[],
                  searchTerm:''
                  };
    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);
    //this.searchResults=this.searchResults.bind(this);
  }
  addTrack(track){
    if (this.state.playlistTracks.find(savedTrack => savedTrack.id===track.id)){
        return;
      }
      this.state.playlistTracks.push(track);
      this.setState({playlistTracks:this.state.playlistTracks});
    }
    removeTrack(track){
      const updatedList= this.state.playlistTracks.filter(savedTrack=> savedTrack.id!==track.id);
        this.setState({playlistTracks:updatedList})
    }
    updatePlaylistName(name){
      this.setState({playlistName:name});
    }
    savePlaylist(){
      const trackURIs =this.state.playlistTracks.map(track=>track.uri);
      Spotify.savePlaylist(this.state.playlistName,trackURIs);
      this.setState({playlistName:'New Playlist'});
      this.setState({playlistTracks:[]});
    }
    search(searchTerm){
      this.setState({searchTerm:searchTerm});
      Spotify.search(searchTerm).then(searchResults => this.setState({searchResults:searchResults}));
      console.log('this is the search term: ' +searchTerm);
    }
  
  render(){
    return (
      <div>
        <h1>Ja<span className="highlight">mmm</span>ing</h1>
        <h1>{this.state.searchTerm}</h1>
        <div className="App">
          {/*<!-- Add a SearchBar component -->*/}
            <SearchBar onSearch={this.search} searchTerm={this.state.searchTerm} />
          <div className="App-playlist">
            {/*<!-- Add a SearchResults component -->*/}
              <SearchResults searchResults ={this.state.searchResults} onAdd={this.addTrack} />
            {/*<!-- Add a Playlist component -->*/}
              <Playlist playlistName={this.state.playlistName}
               playlistTracks={this.state.playlistTracks} onRemove={this.removeTrack}
               onNameChange={this.updatePlaylistName} onSave={this.savePlaylist} />
          </div>
        </div>
    </div>
      );
  }
}

The promise seems to be working. I checked React Developer Tools and the information is being passed to SearchResults, Tracklist, and Track. However, I am getting this warning and also the tracks are not being rendered on the results page. Here is what I see in the props value of Tracklist.js:

If anybody has encountered this problem and would like to shed some light on my issue, I would greatly appreciate it! All the best and take care! Happy coding :slight_smile:

Hi everyone, I actually figured it out haha. I had a typo in my spotify.js code. I used the array brakcets instead of parenthesis ( ) when wrapping the body of the map method:

return jsonResponse.tracks.items.map((track)=>[ //array bracket should be parenthesis
                    {   id:track.id,
                        name:track.name,
                        artist:track.artists[0].name,
                        album:track.album.name,
                        uri:track.uri}
                    ]);  //array bracket should be parenthesis 

It should be like this:

return jsonResponse.tracks.items.map((track)=>(  //parenthesis here is correct
                    {   id:track.id,
                        name:track.name,
                        artist:track.artists[0].name,
                        album:track.album.name,
                        uri:track.uri
                    }
              )); //parenthesis here is correct

Just as an aside, I experimented with using the curly brackets { } instead of parenthesis when declaring the body of the map method and I got a syntax error. I’m still a little bit confused why.

Why are do we have to use parenthesis instead of curly brackets for the map body? I’ve always used curly brackets for map methods. Is it because we are returning an object in this case?

Any help is appreciated! Thanks everyone :slight_smile: