Jammming Project Postmortem

I completed this project earlier this week and ran into my fair share of issues trying to get it to work. Given how frustrating some of these issues were, I thought I would share some of what I learned while working on this so that people running into the same problems have another resource to look into.


Step 11:

Since we used ‘create-react-app’ to generate the initial app template, the App component will be created as a function rather than a class extension and will not contain a render method. My fix was to re-write it as a class extension.

Step 34:

TypeError: Cannot read property ‘map’ of undefined

The undefined in this error message is the this.props.tracks array which is supposed to be passed in from SearchResults. I honestly don’t know what causes this issue; my assumption is that there is some sort of asynchronous process taking place in the background when we’re passing the state from App through SearchResults as props and finally to TrackList. The error is due to the props received by TrackList being undefined. The way I resolved it was by wrapping the return statement in TrackList’s render method within an if statement, a solution I found here after googling the error message.

What it appears to do is allow React to not break on the missing props in TrackList by not rendering while it’s still undefined, and then render when props finally receive its value from SearchResults.

Here’s my render method in TrackList:

Render
render() {
	if (this.props.tracks) {
		return (
			<div className="TrackList">
				{this.props.tracks.map(track => <Track key={track.id} track={track} />)}
			</div>
		)
	} else {
		return null;
	}
}

The alternative is to comment out the Playlist component until Step 39 where it gets the props needed to make it function correctly. When I got to step 39, I removed the if block wrapping, and everything worked fine.

Step 73:

A step to bind the this.search event handler to the ‘Search’ button appears to have been left out. It’s possible I simply missed it somehow, but after doing a search for onClick events in the instructions, I only found 3 of them (add-track, remove-track, save-to-spotify).

I made the following change to the button element in SearchBar.js:

<button className="SearchButton" onClick={this.search}>SEARCH</button>

:rotating_light:
From Step 79 on, you enter hard-mode – there is a big ramp-up in things that you’ll need to brush up on or research. I found it useful to review the asynchronous lessons covering promises and fetch as these technologies are how we’ll be interacting with the Spotify API. We’ll also be using properties and methods of the window object (basically your browser), so I also found it useful to look over its documentation. Regardless, reviewing the hints becomes pretty necessary in this part, and I often reviewed the help-video after I tried to complete a step to see what the end results were supposed to look like as I didn’t want to propagate an error further down that would make troubleshooting much harder.


Step 79:
Match is a string method, and you need to make sure you understand how it works. For one thing, it returns an array, not just a string that matched your regex which was my assumption. Second, it can return more than one match (which would be contained in the array). You’ll need to figure which of the matches within the array you need.

Step 81:

Note that http://localhost:3000/ and http://localhost:3000 are NOT the same. If you add a slash at the end, make sure you have it in both Spotify.js and your Spotify dashboard.

Step 83:

More of an FYI – the redirect URL sends the user to Spotify to login and grant permission to the app to save a playlist to their account. While the answer is in the question, I was trying to see if I can complete this step by only using the Spotify documentation, and built the URL by hand and only put in the required parameters. It turns out the scope parameter is what Spotify uses to determine what your app can do and leaving it out will generate an error that you need to catch in the console.

Step 84:

So don’t make the mistake that I made here – DON’T USE the tracks endpoint guide, DO USE the search endpoint guide.

Step 90 - 94:

Over the course of writing the savePlaylist method, three fetch requests will be made. The first will be an authentication request using the access token you will obtain by using the getAccessToken method written earlier, the second will be a request to create a new playlist in the user’s account, and the third will be to populate that playlist with tracks.

Here’s something to keep in mind – fetch is asynchronous. This means by default, you don’t know the order in which your fetch requests will be resolved. This is a problem for this method as there are dependencies – you won’t be able to create a playlist without being authenticated, and you can’t add tracks to a playlist that hasn’t been created yet. You will need to nest these requests together in such a way that the next one doesn’t kick-off until its predecessor has successfully resolved.


That’s it for now, here’s hoping this write-up helps save some hair-pulling and/or monitors from blunt-force trauma.

4 Likes

i finished the project but i dont get any results. there is neither an error nor any warnings but i cant see the result of my search. the console shows this:
[HMR] Waiting for update signal from WDS…
can you help, please?
edit: your step 73 kinda solved it but now i am getting an authentication error. but i am not even being redirected to the spotify page to get the token. this is the new error:
Access to fetch at ‘http://api.spotify.com/v1/search?q=adele&type=track’ from origin ‘http://localhost:3000’ has been blocked by CORS policy: Response to preflight request doesn’t pass access control check: Redirect is not allowed for a preflight request.
Spotify.js:28 GET http://api.spotify.com/v1/search?q=adele&type=track net::ERR_FAILED
search @ Spotify.js:28
search @ App.js:53
search @ SearchBar.js:12
callCallback @ react-dom.development.js:188
invokeGuardedCallbackDev @ react-dom.development.js:237
invokeGuardedCallback @ react-dom.development.js:292
invokeGuardedCallbackAndCatchFirstError @ react-dom.development.js:306
executeDispatch @ react-dom.development.js:389
executeDispatchesInOrder @ react-dom.development.js:414
executeDispatchesAndRelease @ react-dom.development.js:3278
executeDispatchesAndReleaseTopLevel @ react-dom.development.js:3287
forEachAccumulated @ react-dom.development.js:3259
runEventsInBatch @ react-dom.development.js:3304
runExtractedPluginEventsInBatch @ react-dom.development.js:3514
handleTopLevel @ react-dom.development.js:3558
batchedEventUpdates$1 @ react-dom.development.js:21871
batchedEventUpdates @ react-dom.development.js:795
dispatchEventForLegacyPluginEventSystem @ react-dom.development.js:3568
attemptToDispatchEvent @ react-dom.development.js:4267
dispatchEvent @ react-dom.development.js:4189
unstable_runWithPriority @ scheduler.development.js:653
runWithPriority$1 @ react-dom.development.js:11039
discreteUpdates$1 @ react-dom.development.js:21887
discreteUpdates @ react-dom.development.js:806
dispatchDiscreteEvent @ react-dom.development.js:4168
localhost/:1 Uncaught (in promise) TypeError: Failed to fetch
Promise.then (async)
search @ App.js:53
search @ SearchBar.js:12
callCallback @ react-dom.development.js:188
invokeGuardedCallbackDev @ react-dom.development.js:237
invokeGuardedCallback @ react-dom.development.js:292
invokeGuardedCallbackAndCatchFirstError @ react-dom.development.js:306
executeDispatch @ react-dom.development.js:389
executeDispatchesInOrder @ react-dom.development.js:414
executeDispatchesAndRelease @ react-dom.development.js:3278
executeDispatchesAndReleaseTopLevel @ react-dom.development.js:3287
forEachAccumulated @ react-dom.development.js:3259
runEventsInBatch @ react-dom.development.js:3304
runExtractedPluginEventsInBatch @ react-dom.development.js:3514
handleTopLevel @ react-dom.development.js:3558
batchedEventUpdates$1 @ react-dom.development.js:21871
batchedEventUpdates @ react-dom.development.js:795
dispatchEventForLegacyPluginEventSystem @ react-dom.development.js:3568
attemptToDispatchEvent @ react-dom.development.js:4267
dispatchEvent @ react-dom.development.js:4189
unstable_runWithPriority @ scheduler.development.js:653
runWithPriority$1 @ react-dom.development.js:11039
discreteUpdates$1 @ react-dom.development.js:21887
discreteUpdates @ react-dom.development.js:806
dispatchDiscreteEvent @ react-dom.development.js:4168

Not seeing your code and only seeing this error message, I can only assume that your fetch request wasn’t set up correctly. CORS is usually enabled by default (ie. the mode parameter is set to cors by default), so you may want to make sure you left it as such.

If that doesn’t help, and you use certain extensions like uBlock or Privacy Badger in your browser, you may want to disable them temporarily as well to avoid issues.

Finally, due to changes in the fetch API over time, you also may want to make sure you have the latest version of the browser you’re using.

Another thing I noticed is that your search string has the parameters in a different order than mine. It’s been a while so I’m not sure if it matters, but I posted my search method as a reference in case it helps.

My Search Method
search(term) {
        const token = Spotify.getAccessToken();
        return fetch(`https://api.spotify.com/v1/search?type=track&q=${term}`,
            {headers: {Authorization: `Bearer ${token}`}}
        ).then(response => response.json()).then(jsonResponse => {
            if (!jsonResponse.tracks) {
                return [];
            }
            return jsonResponse.tracks.items.map(track => {
                return {
                    id: track.id,
                    name: track.name,
                    artist: track.artists[0].name,
                    album: track.album.name,
                    uri: track.uri
                };
            });
        });
    }
1 Like

thank you. it worked, had a couple more errors but got through them. and no, the search string parameters didn’t matter. thanks!