Jammming Feature Project TypeError: Cannot read property 'map' of undefined

Hi I am trying to build the feature that when a user loads the page and has their access token given to them The users Playlist to be render as well

here is an image of my page at the moment the far right is where I want this feature to be rendered ‘Your Playlist’

TypeError: Cannot read property ‘map’ of undefined

the this.state{playlistList}) is returning undefined from the promise for Spotify.getUserPlaylist() I cant understand why if I log inside my Spotify.getUserPlaylist() the objext values like playlist.name, and playlist.id I can see that I am getting those values but at the end I cant undertand why it is returning undefined.

My App.js

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


export class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      searchResults: [],
      playlistName: 'My Playlist',
      playlistTracks: [],
      playlistList: []
    };
    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.componentDidMount = this.componentDidMount.bind(this);
  }

  addTrack(track) {

    const 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(savedTrack => savedTrack.id !== track.id);
  
    this.setState({ playlistTracks: tracks });
  }

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

  savePlaylist() {
    const trackUris = this.state.playlistTracks.map(track => track.uri);
    const name = this.state.playlistName;
    Spotify.savePlaylist(name, trackUris).then(() => {
      this.setState({
        playlistName: 'New Playlist',
        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">
          <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} />
            <UserPlaylist />
        </div>
        </div>
      </div>
    )
  }
  componentDidMount() {
    Spotify.getAccessToken();
    // just forcing the user to signup the Spotify acc inorder to have the access token
  }
}

UserPlaylist.js

import React from 'react';
import './UserPlaylist.css';
import { UserPlaylistItem } from '../UserPlaylistItem/UserPlaylistItem.js';
import Spotify from '../../util/Spotify';

export class UserPlaylist extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            playlistList: []
        }
        this.componentDidMount = this.componentDidMount.bind(this);
        
    }
    render() {
        console.log("hello dev");
        console.log(this.state.playlistList); // why is this undefined?
        console.log("Still undefined?")
        return (
            <div className="UserPlaylist">
                <h2>Your Playlist</h2>
                {
                    this.state.playlistList && // if I take this <- off here (this.state.playlist &&) then the 'map' is returned undefined
                    this.state.playlistList.map(playlist => {
                        return (
                            <UserPlaylistItem 
                                key={playlist.playlistId}
                                playlistName={playlist.playlistName}
                                playlistId={playlist.playlistId}
                            />
                        )
                    })
                }
            </div>
        )
    }
    componentDidMount() {
        Spotify.getUserPlaylists().then(playlistList => {
            this.setState({ playlistList: playlistList })
            console.log(playlistList);
        }) // why wont this set the value playlistList from the value I get off Spotify.getUserPlaylist()
        // console.log(Spotify.getUserPlaylists()) //what is wrong with the promise??
    }
}

UserPlaylistItem.js

import React from 'react';
import './UserPlaylistItem.css';


export class UserPlaylistItem extends React.Component {
    render() {
        return (
            <div>
                <h3 id={this.props.key}>{this.props.playlistName}</h3>
            </div>
        )
    }
}

Spotify.js

const clientID = "HIDDEN";
const redirectURI = "http://localhost:3000/";

let accessToken;
// const headers = {Authorization: `Bearer ${accessToken}`};
let userId;


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 it expires.
            window.setTimeout(() => accessToken = "", expiresIn * 1000);
            window.history.pushState('Access Token', null, '/');
            return accessToken;
        } else {
            let accessURL = "https://accounts.spotify.com/authorize?client_id=" + clientID + "&response_type=token&scope=playlist-modify-public&redirect_uri=" + redirectURI;
         //                   "https://accounts.spotify.com/authorize?client_id=" + CLIENT_ID+ "&response_type=token&scope=playlist-modify-public&redirect_uri=" + REDIRECT_URI"
            console.log(accessURL);
            console.log("access URL is recieved ")
            return window.location = accessURL;
        }
    },
    search(term) {
        accessToken = this.getAccessToken();
        const headers = {'Authorization': `Bearer ${accessToken}`};
        
        console.log(headers)
                
        return fetch(`https://api.spotify.com/v1/search?type=track&q=${term}`, {
            headers: headers
        }).then(response => {
            console.log("response search " + response);
            return response.json()
        }).then(jsonResponse => {
            console.log(jsonResponse);
            if (!jsonResponse.tracks) {
                return [];
            } else {
                return jsonResponse.tracks.items.map(track => ({
                    id: track.id,
                    name: track.name,
                    artists: track.artists[0].name,
                    album: track.album.name,
                    preview: track.preview_url,
                    uri: track.uri
                }));
            };
        });
    },
    async getCurrentUserId() {
        if (userId) {
            return userId;
        }
        console.log("calling get user id ")

        accessToken = this.getAccessToken();
        const headers = {Authorization: `Bearer ${accessToken}`};
        console.log(headers);

        return await fetch('https://api.spotify.com/v1/me', {
            headers: headers
        }).then(response => response.json()
        ).then(jsonResponse => {
            userId = jsonResponse.id;
            // console.log(userId);
            return userId;
        })
    },
    savePlaylist(name, trackUris) {
        if (!name || !trackUris.length) {
            return;
        }
        
        accessToken = this.getAccessToken();
        const headers = {Authorization: `Bearer ${accessToken}`};

        // if (!userId) {
        //     userId = this.getCurrentUserId();
        // }  
        userId = this.getCurrentUserId();
        
        return userId.then(() => {
            fetch(`https://api.spotify.com/v1/users/${userId}/playlists`, {
                headers: headers,
                method: 'POST',
                body: JSON.stringify({ name: name })
            }).then(response => response.json()
            ).then(jsonResponse => {
                console.log(jsonResponse);
                const playlistId = jsonResponse.id;
                console.log(playlistId);
                console.log(userId);
                console.log(headers);
                console.log(trackUris);
                return fetch(`https://api.spotify.com/v1/users/${userId}/playlists/${playlistId}/tracks`, {
                    headers: headers,
                    method: 'POST',
                    body: JSON.stringify({ uris: trackUris })
                })
            })
        })
    },
    getUserPlaylists() {
        // if (userId) {
        //     return userId
        // }
        accessToken = this.getAccessToken();
        const headers = {Authorization: `Bearer ${accessToken}`};
        userId = this.getCurrentUserId();
        console.log(headers);
    
        return userId.then(() => {
            fetch(`https://api.spotify.com/v1/users/${userId}/playlists`, {
                headers: headers,
                method: 'GET'
            }).then(response => response.json()
            ).then(jsonResponse => {
                if (!jsonResponse.items) {
                    return [];
                } else {

                // console.log(jsonResponse);
                // console.log("json Response");
                // const jsonResponseItems = jsonResponse.items;
                    return jsonResponse.items.map((playlist) => ({
                        playlistName: (playlist.name), //If i console.log it I will see the  result value
                        playlistId: (playlist.id), // If I console.log it I will see the ID Value
                    }))
            }  
            })
            
        })
        
    },
}

export default Spotify;

You seem to be trying to track the state of playlistList in two different places: both in the App and UserPlaylist components.

This is probably causing the conflict, as:

  • the UserPlaylist component is rendered by both components and this will refer to different states in each render.
  • state of the playlistLists will only have been updated to one of these places (not both), and not necessarily the same place the .map() function is trying to access it from - so map is probably trying to map the undefined/empty version.

I would suggest reviewing your other components (e.g. the existing playlist & search components) to see how state is managed between parent and child components successfully.

Consider, how does the App.js parent component manage state in partnership with its child components?

App.js

How does it ensure that search results are displayed in the SearchResults component, and that playlist names and tracks are displayed in the Playlist component?

How do you do replicate this process for the UserPlaylist component?

I managed to solve it by changed my whole Spotify.js to a async function with await thus I was able to see what I was writing it made the the whole thing easier than having fetch.then.then.then

this project was enjoyable i put all states in the App.js and handled the children with props 6days it took me, im happy its done now I feel more comfortable with react and using api fetching

My Updated Spotify.js

const clientID = "HIDE";
const redirectURI = "http://localhost:3000/";

let accessToken;
let userId;

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=([^&]*)/);
        // console.log(accessTokenMatch);
        // console.log("access Match is above");
        // console.log(expiresInMatch);
        // console.log("expires in Match is above");
        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 {
            let accessURL = "https://accounts.spotify.com/authorize?client_id=" + clientID + "&response_type=token&scope=playlist-modify-public&redirect_uri=" + redirectURI;
         
            // console.log(accessURL);
            // console.log("access URL is recieved ")
            return window.location = accessURL;
        }
    },
    search(term) {
        accessToken = this.getAccessToken();
        const headers = {'Authorization': `Bearer ${accessToken}`};
       
        return fetch(`https://api.spotify.com/v1/search?type=track&q=${term}`, {
            headers: headers
        }).then(response => {
            // console.log("response search " + response);
            return response.json()
        }).then(jsonResponse => {
            // console.log(jsonResponse);
            if (!jsonResponse.tracks) {
                return [];
            } else {
                return jsonResponse.tracks.items.map(track => ({
                    id: track.id,
                    name: track.name,
                    artists: track.artists[0].name,
                    album: track.album.name,
                    preview: track.preview_url,
                    uri: track.uri
                }));
            };
        });
    },
    async getCurrentUserId() {
        if (userId) {
            return userId;
        }
        console.log("calling get user id ")

        accessToken = this.getAccessToken();
        const headers = {Authorization: `Bearer ${accessToken}`};
        // console.log(headers);
        try {
            const response = await fetch('https://api.spotify.com/v1/me', {
                headers: headers,
                method: 'GET'
            });
            if (response.ok) {
                const jsonResponse = await response.json();
                userId = jsonResponse.id;
                return userId;
            } 
        } catch(err) {
            console.log(err);
        }
    },
    async savePlaylist(name, trackUris) {
        if (!name || !trackUris.length) {
            return;
        }
        
        accessToken = this.getAccessToken();
        const headers = {Authorization: `Bearer ${accessToken}`};

        // userId = this.getCurrentUserId();

        try {
            userId = await Promise.resolve(this.getCurrentUserId());
            const response = await fetch(`https://api.spotify.com/v1/users/${userId}/playlists`, {
                headers: headers,
                method: 'POST',
                body: JSON.stringify({ name: name })
            });
            if (response.ok) {
                const jsonResponse = await response.json();
                const playlistId = jsonResponse.id;
                console.log(playlistId)
                return fetch(`https://api.spotify.com/v1/users/${userId}/playlists/${playlistId}/tracks`, {
                    headers: headers,
                    method: 'POST',
                    body: JSON.stringify({ uris: trackUris })
                })
            }
            
        } catch(error) {
            console.log(error);
        }

    },
    async getUserPlaylists() {
        
        accessToken = this.getAccessToken();
        const headers = {Authorization: `Bearer ${accessToken}`};
        // userId = this.getCurrentUserId();
        
        // console.log(headers);

        try {
            userId = await Promise.resolve(this.getCurrentUserId());
            const response = await fetch(`https://api.spotify.com/v1/users/${userId}/playlists`, {
                headers: headers,
                method: 'GET'
            });
            if (response.ok) {
                const jsonResponse = await response.json();
                // console.log(jsonResponse);
                const playlistList = jsonResponse.items.map(playlist => ({
                    playlistName: playlist.name,
                    playlistId: playlist.id
                    
                }));
                console.log(playlistList);
                return playlistList;
            }
            } catch(err) {
                console.log(err)
            }
        
    },
}

export default Spotify;

async makes it look alot cleaner that ajax and its .then()

just one small issue
console.log(playlistList);
return playlistList;

This console.log(playlistList) if I leave it on I have somewhat created an endless loop in the console so when I remove it comment it out its gone.
The problem I think could be causing this Im using in my App.js for this Spotify.getUserPlaylist() the two lifecycle methods the componentDidMount() and componentDidUpdate() I dont know maybe they are causing the endless loop? Would I therefore be making an endless api GET request to Spotify and would they be annoyed by that?