Michael Bruns
September 12, 2018
TL;DR:
React Spotify Blog Demo
React Spotify Blog Source Code
Prerequisites
We're using Node JS and npm in this project so make sure you have them installed before we get started. I'm also going to be high-level on React, CSS Grid or Flexbox so it'd be good to have some familiarity there before getting started, and I cannot live without ES7 async/await for dealing with asynchronous code like AJAX requests. If any of those terms sound strange to you I cannot recommend the tutorials from Wes Bos highly enough - the smartest and most engaging Canadian I know of!
Finally - if you want to actually be able to press play and hear the music you'll need a Premium Spotify membership. If you don't have one that's OK too - you'll just have to manually open your playlists in Spotify and open the new one
we made.
Planning it out
I'm old school so I always like to start out with a literal piece of paper to plan out exactly what I want something to look like and do. My handwriting is terrible though, so here's basically what my paper looks like:
I want two main features: First is that I want to be able to write an homage to one of my favorite artists and have a button directly connecting them to a playlist of their albums on Spotify so the reader can start listening immediately. There should also be a way for them to read posts with homages about other artists. Secondly, I think it's criminal that Spotify doesn't let you natively shuffle by album (how are you supposed to get into the groove of the artist?) so the user should be able to shuffle their own playlists by album. If they only have a few tracks from an album they should also be able to automatically bring in the other songs from that album.
Another key planning step when using React is to make a list of the components that you're going to need. Components are small modules of a user interface that can be combined to make the total UI. Not every element has to be its own component, but
we want to get the major stuff. Based on my drawing (a rough wireframe) we'll need about 7 components:
App/Container/Wrapper
Featured Artist Post
Author
Menu to select other posts
Spotify Container
Connection component (when user isn't logged in yet)
Playlist Chooser
Picking the right tools
Now that I know what I want, the next step is seeing if there are any existing tools or libraries to give me a head start. There are several, so a lot of the work is done for us!
Cosmic
Excellent content management system that exposes a simple flexible API with the data to our app. Cosmic is way simpler and lighter weight than something like Wordpress, but gives us the flexibility of adding new artist homages later without redeploying the app.
create-react-app
I'm a React guy, but it can be complicated to get all the configuration set up for a single page app like this. Facebook has a great repo and tool called create-react-app that is ready to customize.
CSS Grid, Flexbox
I'm not old-school when it comes to CSS: Grid and Flexbox are much more predictable than old-school float:left. I love starting a fresh project with CSS systems that actually make some sense
Material-UI
Flat design and Material UI are all the rage, and since I'm not much of one for presentation it's great to have some decent react-based design components available.
Spotify API
Spotify has one of the cleanest and most well-documented REST APIs out there. And it's even easier to use in a javascript front-end using an excellent wrapper library called from JMPerez called spotify-web-api-js
Starting with create-react-app
Let's get started by cloning create-react-app. They have easy instructions at the repo. If your version of npm is 5.2 or higher you can just open up a terminal, navigate to a new directory for the project and type.
I'm running npm version 4.2.0 so I have to install a create-react-app command line tool (CLI tool) first, then use the CLI tool to create the app. Remember to also do a git init once it's done so you can save your work.
Start the example up with npm start quick to make sure everything worked OK. If it looks like this then great work! If not, you're gonna have some problems... I don't like the file structure that create-react-app gives us, so I like to rework it to put our components in their own subfolder underneath src. That way we can keep helper files that don't affect the UI directly in src, and separate components in their own folder so they're easier to browse.
If you do the same, remember to go into index.js and change the import reference for the App component so you index.js can find it in the new components subfolder.
Setting up the structure in
Let's set up the overall structure first with some dummy components, and then we can drill down individually. We're just going to repurpose the component as our top-level wrapper. Pull the JSX code out of and replace it with this:
The JSX/HTML code only tells the browser what the elements are, so we need some CSS. We're going to use CSS Grid to lay out a structure similar to the wireframe. A media query for screens under 800 pixels puts everything into one column for mobile. Let's just copy this code into App.css for now and the comments explain some highlights.
Let's see it! Using chrome devtools and highlighting will show you the grid. The reason we did 5 rows instead of 3 is because we want the Spotify player to be able to reach all the way from the header to the footer, but on the left we want featured post to have 2/3 of the vertical space and other posts only 1/3. We probably could have gotten away with 4 rows, but 5 seemed right to me.
Ok! Let's build out first real display component: First things first - let's get some pretty components from Material UI (aka Mui). Their getting started page walks us through it. First we'll need to install their node package.
We'll also need to add the Roboto font to our index.html so that everything will display properly.
Mui has a great background component called Paper that is similar to Bootstrap panels. They also use a Typography component for headers and subheaders. It looks classy, and I read that using Typography makes it easier to apply their "themes" later if you want to change your look.
Let's go ahead and create two files in our components folder: FeaturedPost.js and FeaturedPost.css. I usually copy App.js and App.css and just replace 'App' with 'FeaturedPost' and then edit, but your call. Here's what those files should look like:
If we're in the same place, the app should look about like this:
Ok, that's great. But nothing is more boring that static content. How about we hook some data up to this thing? Data means content, which means we're ready to set up Cosmic!
Getting dynamic content from Cosmic
Cosmic is a backend content management system that gives us the best of app development and content. On the content side we get all the convenience of a Wordpress-like interface that a marketing team or blogger can use to easily add new posts, authors or really anything they would like. I actually find it even easier that Wordpress because it's cleaner and simpler. But on the app development side we get a very clean, simple json-API that we can feed into our crisp React app (or any other framework you like). We don't have to deal with php and plugins and all the clutter that Wordpress forces on us and that bloats websites. Yay!
First we create a new free account at Cosmic. You'll have to verify your email or you can use GitHub to sign in. I recommend using GitHub to make deployment easier later.
Once you have your account set up you need to create a new Bucket, which is basically where all of the content data for our app will go. You'll use a unique name for the bucket (I used oldschoolshuffle). Select Start from Scratch and save the Bucket.
Inside a Bucket, data is organized into Objects, Object Types, and Files. It's a very lightweight and flexible system. What are each of these?
Object Types are basically schemas: they describe a class of thing that you may want to use in your app, and what details about that thing are important to know. In our case we need Posts and we need Authors to write those posts. If you were doing app about world geography you might have a Country Object Type, or if you were doing a travel agency app you might have an Object Type for Airplane flights or Hotels. The important thing is an Object Type has Metafields (properties) for everything you want to know about that type of thing.
Objects are any discrete thing that you may want to refer to in your app. A post about Kacey Musgraves is an Object that is of the Post Object Type. Flight AA2345 Austin-to-Boston might be an Object of the Flight type in a travel agency app.
Files are media or binary files (sometimes called 'static' files) that you might need to use. Usually these will be pictures, but they could be downloadable content, whitepapers, mp3s or whatever.
You can also set up multiple Users, but User in this sense is people who have permission to change things in Cosmic (like you as a developer, or a blog post author), not users in your app.
We'll set up our Object Type for Authors first, since we want everyone to know who loves old-school listening. Based on our wireframe an Author needs a name and an avatar image so we have a little more flair. Click on Add Object Type in the left-hand menu, then under Basic Settings let's do Author for the singular name and Authors for the plural name.
Next click on the Metafields Template tab. Every Object Type gets a title and a content Metafield automatically, so we don't have to add a title Metafield, but we do need to add a Metafield for the avatar. Click on the blue plus sign and you're presented with a menu of different types of Metafields. We want an image, so choose that.
Remember, we aren't making a specific Author yet so resist the temptation to drag and drop an image unless you want to set a default. What's more important is that we set the title to avatarImage so we can assign things to it later. I like camelCaseToWriteThingsThatIllHaveToUseInMyCodeLater but you don't have to in this case because Cosmic will automatically assign a key that we'll use in the code.
Once you're done, click the Save Object Type button. Now we can create our two specific Authors. You should now see a new Authors folder under the left-hand menu. Click on that and you should have a blank section and an Add Author button. I recommend you click that button.
Nice work! Put the author's name (I used Elvis Costello) as the title. You'll also see that our avatarImage Metafield that we made in the Object Type carried over, and now we can choose an image, such as an old-school RV trailer, to use as the avatar.
When we're done push the Publish button to save this Author and close the screen.
Do the same for a second Author, just for fun. Then we need to build the Object Type for our Posts by clicking on Add Object Type from the menu again. Put Post/Posts for singular/plural and go the the Metafields Template tab.
We're adding two Metafields for Posts: spotifyArtistId and author. The first, spotifyArtistId, is going to be a Plain Text Input, and that's where we're going to store a unique Id that Spotify uses to identify musical artists in their API.
The second Metafield (author) is going to be a Single Object Relationship Metafield, which means that we are going to connect Posts with a SINGLE Author of the Object Type we created earlier.
Make sure for the author metafield you choose Limit Search by Object Type and choose Authors from the dropdown menu. It doesn't make sense for a Post to be the author of a Post. Once that's done click Save Object Type and we'll return to the dashboard.
Pro tip! If you made Posts before Authors (jumping the gun!) you can go back and EDIT your Post Object to add the author Metafield. Choose Posts from the menu and clicking the gear icon. Took me a while to figure that one out!
Ok, now we just need to make a few posts and then we'll be done with the initial content setup! Click on Posts, then App Post, and let's create 3. Use the artist name for the title, put your homage to them in the content section (go ahead, I'll wait) and choose an Author from the dropdown menu for each. Note that the content area is HTML-enabled, so you can get a little creative in there. You'll also need the spotifyArtistId, which looks something like this: 70kkdajctXSbqSMJbQO424. You can look up the ids for your favorite artists here or you can just use these:
Kacey Musgraves: 70kkdajctXSbqSMJbQO424
Ryan Bingham: 31z9f9AyPawiq0qlBO1M3i
Ted Leo: 5hbH3dvtk49g07qpc1QwPe
WE'RE FINALLY DONE WITH CONTENT! What a brain workout. Let's go back to something simple like coding mobile-response single page applications with third-party data.
Wiring up Cosmic Content
First off: if you aren't comfortable with React you should stop reading now and go check out Wes Bos' React for Beginners course and you will literally come out as an expert. Like we did with the Grid CSS stuff I'm going to try and stay top level.
Sound good? Ok, let's start where all good things in React start - at our top-level component. In React everything is about state (internal conditions in a component) and props (data or functions that get passed down to child components). Let's add a constructor to .
The properties in this.state are everything that we're going to need from Cosmic to make the blog/homage part of this work:
dataReceived will start false and become true after we get out data from Cosmic. This lets us wait to render our posts until we get the data we need to render them properly.
posts is an array where we'll store all the posts we get from Cosmic.
authors is an array where we'll store all our authors from Cosmic.
featuredPostIndex is an integer that we'll use with the posts array to determine which post to show in the Featured Post component. The other posts will be listed in the Other Posts component.
otherPosts will always be a subset of posts, and will not include whatever post is currently featured.
Now we need to connect our React app to our Cosmic API and get the data that we entered back in the CMS. React components really shouldn't do too much heavy lifting, so for the sake of being modular and readable go to your src folder and add a new file called cosmicFunctions.js (this will be outside of the components directory).
Cosmic offers a really handy node module on npm that makes it simple to get the data we need without dealing with CORS (cross-origin-request) issues or ajax requests. Let's install that with npm first.
We also need to add a .env file with the name of our Bucket (also called a slug). You don't want to expose that sensitive data in your code, so we store it in an environmental variable that you don't upload to your public repository. Fortunately create-react-app makes that easy, so you just need to create a .env file in your root directory. Make sure it's in the ROOT directory, NOT in your src directory, and that it starts with a dot. We also need to start the variable name with REACT_APP_ otherwise create-react-app will ignore it.
If you don't know your Bucket slug click on Settings -> Basic Settings in your Cosmic dashboard and you'll see it in the second line.
Now that we're set up we'll import the Cosmic node module into our new cosmicFunctions.js file and add 2 functions getCosmicJsData and organizeCosmicJsDataByObjectType.
Let's look at getCosmicJsData. First you'll notice export async before we define the function. export lets us use this function later in . async enables us to use asynchronous methods within the function, in our case an ajax API request to Cosmic. Async means that we send the API a request, go about our business, and when we get a response back then our app will return to the function and put the data to use.
This async stuff used to be a real pain in the butt but now we can simply put the keyword await before any asynchronous method and the code will automatically wait for a response with useful data before continuing. It's awesome.
Let's look closer at the 10th line:
Wow, lot's going on there. Here's the logic: bucket.getObjects() is an async method that tells Cosmic to send us a payload of everything they have. Because we don't have much data, it's easiest just to get everything from the API now (when the page first loads) and sort it ourselves. As is common with APIs, a lot of that payload is metadata we don't need. So, we wrap that call in parenthesis and just take it's objects property, which is an array of all the Posts and Authors that we have.
Now we have the data we need as a jumble of all our Object Types, but to store it cleanly in our state we want to be more organized. That's why we pass arrayOfAllObjectsInBucket to the organizeCosmicJsDataByObjectType function. It maps through the array and returns an object with keys based on the different Object Types and arrays of the Objects of that type. We could have just looked for Posts and Authors, but this way is more flexible and wouldn't need to be changed later if we decided to rename Posts to HomagesToGreatness, or something like that.
Anyway, that transformed data looks like this:
See how nicely that will feed in our state in ? Let's hook it up! React components have built in lifecycle methods where we can tell it what to do at loading, closing, rerendering, etc. The best time to get outside data into a component is after it is first mounted into the DOM (if you don't know what the DOM is your should probably just leave now) so let's add a componentDidMount method in and put our getCosmicJsData function in it.
A few key points on this code:
remember you have to import our cosmicFunctions helper library to access getCosmicJsData. It's also up a directory, so you have to use ../
componentDidMount has to be an async function. It contains getCosmicJsData, which has to be async because IT contains bucket.getObjects(), which is inherently async.
I try to always wrap async code in try/catch blocks since you never know if they'll fail.
The weird const {posts, authors} = ... format is ES6 destructuring, and basically the same as posts = someObject.posts. Destructuring is great for breaking down objects.
In React you can NEVER assign properties to this.state except in the constructor. Doing so violates a concept called immutability but suffice to say it breaks React. Instead you always use this.setState and React takes care of it for you (thanks React!)
Let's also check out this gnarly line:
What's going on here? Short answer is it tells to not render until after we receive the data we need. React calls this conditional rendering, and it prevents ugly blank components or errors.
In practice, it's just a ternary operator that passes the JSX React component if this.state.dataReceived is true, or renders an empty string if not. Since we update this.state.dataReceived in componentDidMount React now knows when it's safe to show the FeaturedPost.
Last scary-looking thing:
So is just the React component we made earlier. It needs the data in a Post object to display the artist name and the homage. this.state.posts is the array of Post objects we got from Cosmic.
this.state.featurePostIndex is just an array index to pick which post in the array we're interested in, like posts[0]. So all we're doing is identifying a Post object and passing it to FeaturedPost as a property (props) called post. That way we can use the data in the child component, even though we got the data in the parent component!
Let's move on to and actually display the data.
Displaying our homage to Kacey Musgraves
Let's just go straight to the code and dive in here. We're going to modify the code we wrote earlier to pull in the data from the post property that we just passed down.
We're not going to tackle the Play Discography button yet, but we replaced the artist Cake with {this.props.post.title} so now we get dynamic post titles/artist names. Content we also replaced with dynamic content, but it's more complex because the content Metafield from Cosmic is HTML, not just simple text.
React is very skeptical about directly injecting HTML from outside sources into your app because it's a significant security risk, but we're ok with it because we control our Cosmic data and because this is just an example application. To utilize outside HTML you have to use the dangerouslySetInnerHTML property on a div element and you also have to pass it a uniquely formatted object, not just the HTML code as a regular string. Basically they make it cumbersome so you don't do it by mistake.
We also see a new component, which passes along the author name and image too. As was given to you by your parent, so you shall pass on to your own child one day.
is pretty straightforward so we'll just copy over the code for that. Remember you'll need to create files in your components directory for Author.js and Author.css. and remember to import Author.js at the top of FeaturedPost.js.
Do an npm start and see what it looks like. Hopefully something about like this?
Boom shaka laka. Nearly done with the content stuff! Unfortunately is probably the trickiest part of the content stuff.
Let's do it in 2 parts. Before we get dynamic with it, let's lay out a basic dumb component using Material UI's Expansion panel component. This means making OtherPosts.js and OtherPosts.css files in our component directory. We'll also need to go into our component and replace our placeholder text with the newly created dummy component. Here's the code:
Lots of code, but nothing too scary. The expansion panel stuff looks strange, but it's basically the same structure as a
- with nested
- elements and a label. If all this came together properly you should have a clean clickable panel that expands to show our dummy posts.
Let's get the data in there. When we updated we passed the otherPosts props down to . Since otherPosts is an array of the other posts we should be able to replace our dummy elements by mapping over the otherPosts array and using the data from each posts. Let's do that now:
That's not too bad. Using {postObject.title} to feed in the post names is simple. The only surprising part is that key property, where we're incorporating the array index, which wasn't in our dummy component. The reason for that is that React uses the key property to differentiate between similar elements with the same parent (like
- elements), so we need a unique key to help React keep them straight.
That wasn't much harder than . So why do I think is so tricky? Well Wilbur, don't we want you to be able to click on another post and change ? Which post was clicked on seems like internal state for . Wait, is a different component... that means we're passing state upstream! How the heck do we do that?
Here's the mental Jiu Jitsu - you don't pass the state up from . Remember this.state.featuredPostIndex that lives in the parent (ie )? You write a method in to change that state and pass down that method to .
How meta is that? Want something more concrete? Let's code it. First :A few changes to note here:
Added changeFeaturedPost method
This is the method we'll pass down to . can then call the method using the proper index for the Post. That will in turn change the state.featuredPostIndex in . Since passes state.featuredPostIndex to then will update. Simple as pie!
Passed allPosts to
is going to need the array of all Posts so it can determine the index of whatever post is selected. We'll explain this is more detail in just a minute.
Passed the changedFeaturedPost method to
By passing the function itself, not the state, we enable to call it. Remember to pass it as an arrow function instead of just {this.changedFeaturedPost} otherwise React will get confused and clicking a Post will return the function itself, not the RESULT of the function.
Now let's update
The rubber finally hits the road with that nice handleClick method! Here's what's happening:
First we have to pass the handleClick method into the onClick method for the Post. Remember onClick is an event listener so you have to pass the event even if we aren't using it.
This method is called when a Post is clicked in , and receives the postObject of the Post that is clicked.
To call the changeFeaturedPost method we passed down from we actually need to look through the array of all the Posts (which is why we passed it down from as props.allPosts) and return the INDEX that matches the Post that was just clicked. Javascript has a great array.findIndex function where we can define what object properties (in our case that we're looking for and return the index.
Once we have that index, we simply call our passed-down changeFeaturedPost method with it and voila - React does the rest!
If everything went well we should now be able to click on a post, see the featured post change to what was clicked and our list of other posts adjusted.
One point before moving on to Spotify: you're not the only one who hates passing functions between components. This app is simple enough to do it that way, but for apps with more than a dozen or so components I highly recommend using Redux to manage your state and props. The idea is basically like having a library with all your properties and function in one place, and then every component has a library card to go get whatever function it needs. It requires a lot of setup, but once you get past that initial hurdle it's SO worth it. You might also check out React Context as another solution to the problem.Part 2 - The Attack of the Spotify Integration
We're now going to start with the second part of our Old School Shuffle app, which is the integration with Spotify. We're starting again with a brainstorm on a piece of paper, but this time we'll focus on back-end first and deal with front-end components at the end. What do we need to be able to do with Spotify?
The Spotify Plan
What do I need the API to do?
Get access to a user's Spotify
Get their playlists
Get the tracks for a specific playlist
Get the albums for a specific playlist
Get the tracks for a specific album
Create a playlist
Shuffle a playlist
Play discography for a specific artist
Get the albums for an artist
Is any of this work already done and available to build on top of?
Yes! The excellent spotify-web-api-js wrapper library by JMPerez!
Registering your app with Spotify
Spotify has a really all-start API that is extremely well documented and they handhold developers through it with their Web API Tutorial, but we'll try and get started quickly. First you have to have a Spotify listener account, which you probably have if you're here. After that, you'll need to go to your Dashboard to register as a developer and also register your app by getting a Client Id.
The dashboard looks like this after you log in:They walk you through registering the app and getting a Client Id. If you're making a non-commercial app (an open-source app like this) it's free, but I think they charge you for commercial access. When you're done you get a Client Id on your dashboard.
We're going to need that Client Id (I blurred mine), so go ahead and save it in your .env file as REACT_APP_SPOTIFY_CLIENT_ID. If you didn't do it as part of setup, we also need to set up a "Redirect URI" for our app. Click the Edit Settings button in your dashboard and you should see a pop-up like this:
You will need to add both your development environment (should be http://localhost:3000 if you're using create-react-app) as well as your production environment URL when we get to that later. Go ahead and save that in your .env file too as REACT_APP_SPOTIFY_DEVELOPMENT_REDIRECT_URI and REACT_APP_SPOTIFY_PRODUCTION_REDIRECT_URI. You can check your production URI in your Cosmic dashboard.
When we send our login request Spotify will use that Redirect URI to make sure it's really us. Now your .env file should look kinda like this:
While we're getting things set up, let's go ahead and install spotify-web-api-js with npm, as well as some specific Lodash helper functions we'll be using.
Let's also create a spotifyFunctions.js file in the src directory (next to cosmicFunctions.js). It's gonna have a LOT of code in it though, so let's just import our libraries for now and then talk about how Spotify's authentication works.
Authentication
Authorization is one of those things that every web developer dreads.You have to do it all the time, but it tends to be a minefield of Cross-Origin (CORS) errors, OAuth2 servers, Json Web Tokens (JWT), handshake tokens, refresh tokens, and Authorization HTTP headers.
All of these are doable, but they make me want to throw up a little bit, especially on a small demo application. Spotify supports 3 different authentication methods but we're fortunate that they offer an Implicit Grant flow. It's not very secure, but we're talking about music playlists not bank accounts, and it's only good for one hour so you can only cause too much trouble. It's perfect for us because it doesn't require any server-side code. That's handy because create-react-app obscures the express routes by default. You can set up a proxy server with it, but that seems overkill for just this authentication.
Here's Spotify's diagram of how Implicit Grant works.Unlike OAuth/OAuth2 or JWT that require ajax requests, Implicit Grant is super simple and works off of a query string, which is a way to pass data in the URL of a webpage. In effect, what we do is make our login button an external link to Spotify's authorization URL, including data like our Spotify Client ID in a query string. By following the link the user actually leaves our page, and goes to a Spotify-hosted page to give our app access permissions.
If they agree (or if they're already logged in to Spotify and gave us permissions earlier) Spotify will examine the data against the app we registered in the database and then they will redirect the user back to our page, but including a query string with an authorization token. All we have to do to access the API is include that token (BQAHoHp... in the screenshot) in our API requests. We actually don't even need to do authorization headers because spotify-web-api-js will do it for us.
Let's try it out now by building 2 of our 3 Spotify-related components:is an outer wrapper for our Spotify-related UI
is what we'll show when the User isn't logged into Spotify yet. Basically just a login button.
lets the user choose from their exist playlists and shuffle them by album. We'll tackle this one later.
We'll also need to update by replacing our dummy "Spotify Player Here" with . And we're going to add a few functions to spotifyFunctions.js. Here's the code.
We won't spend much time on these components: in we replaced our dummy Spotify placeholder text with . has state for whether we are logged in and, if so, what our access token from Spotify is. If state.loggedInToSpotify is false then we show the component (basically just a log in button), or shows a placeholder with the access token.
also has a componentDidMount lifecycle method that checks at each render whether we are logged in using a function from our spotifyFunctions.js library. Here's that code too.Let's start with logging in. redirectUrlToSpotifyForLogin is just a string concatenator to build a URL with query string to tell Spotify who our app is and what permissions (Spotify calls them scopes) we want to ask the user for. It also includes our Client Id and Redirect URI that we registered with Spotify. You can see in that we simply link our login button to this generated URL.
checkUrlForSpotifyAccessToken is also very simple. It simply checks the URL for a query string with a parameter called access_token. This works because when we first initialize our app our URL will be http://localhost:3000, but after we log in, Spotify will redirect to our app using our Redirect URI but with their authorized query string attached. It'll look something like http://localhost:3000/#access_token=whatever. At that point will be rerendered again, see the access token, and render the access code in text instead of the connect to Spotify button. Easy peasy!
FYI getHashParams is just a generic helper function to parse query strings from the URL.
Now we have everything in place we need to log in to Spotify. Here's what it should look like.The Spotify UI
So, now that we're logged in we have to add and wire it up. The user should be able to choose from a list of their playlists and produce a new version shuffled by album. In case your playlist has SINGLES on it (heretic!) we'll also include an option to automatically add in the whole album. Here's what it should look like:
You're an old hand by now at the front-end React code, so I'm just going to give you the code all at once instead of spoon feeding. If you have questions about it you can ask me in the comments. is heavy on Material UI, especially their Menu component, and this tutorial can help you make sense of that if you get turned around. Don't forget about wiring it into .You'll notice a lot of references to our spotifyFunctions.js helper library in there, so it won't render until we tackle that part. That is where I want to focus next.
Meet Mr. Spot: A iffy API
As a quick recap, here's what we want the API part of the app to do:
The Spotify Plan
What do I need the API to do?
Get access to a user's Spotify - Done
Get their playlists
Get the tracks for a specific playlist
Get the albums for a specific playlist
Get the tracks for a specific album
Create a playlist
Shuffle a playlist
Play discography for a specific artist
Get the albums for an artist
Here's all the code. Get an overall feel for how it matches up with our requirements, but we're going to go through each function so don't worry if you don't understand everything. I apologize in advance that it could probably stand a little refactoring.Alright - let's take it from the top!
We addressed the imports from a little bit ago, but we need an actual instance of the spotify-web-api-js wrapper library to use it, so we're creating that here and calling it spotifyApi.
These are the authentication functions we addressed earlier. Nothing more to say here.
Once we have our access token we need to pass it to our spotifyApi instance, and then we don't have to worry about remembering it for later function calls. We have to export this function because it is called in componentDidMount in .
Once we have the access token we want to get an array of the user's playlists that they can choose from. Like all functions that call 3rd-party APIs, this has to be an async function. Our spotifyApi has a built-in getUserPlaylists method but Spotify gives us lots of information about the playlist that we don't need.
To thin that down we can map through the array of playlist objects we get from the API and return an array of simpler objects with just the id and name of each playlist using ES6 destructuring. We can feed that to state.playlists in and use it to populate our menu.
Notice we also have an error handler that returns "Can't Download your Playlists" if it can't connect to the API. That feeds into the menu too, and so the user gets a somewhat helpful message instead of a crashed app if there are connection issues.Once a user chooses a playlist we need to find out what tracks are on that playlist. We need the name and id of the track, the name and id of the album it's on, what track number it is on that album, and the name and id of the artist that sings that song. Spotify also saves a URI we can just drop into a browser to get the track, so we'll save that too.
This is very similar to getSimplePlaylistTracks, but instead of receiving a playlistId we pass it an albumId, albumName and albumUri. This is helpful for when the user wants to add discography because we can loop through their playlists, get the albums for each track, and then use this to get all the other tracks from the same album.
Both functions look very similar, but are subtly different because the Spotify API responses for albums are structured slightly differently than their data on playlists. There's probably there is a clever way to refactor them into one function - if you come up with it let me know!Just like we said in the last section, this function receives the output of getSimplePlaylistTracks and produces an array of albumIds from the tracks in that playlists. That can be fed into getSimpleAlbumTracks if we need the full discography or just return the albumId if we just need to sort a playlist by album. The returnSimpleArray flag lets us decide which kind of output we want.
Notice that we get the array of albumIds first, then remove duplicates from the array using the Lodash uniq function, then shuffle it. It's more efficient to shuffle the albums first before we add tracks to them.This is just a general helper function to shuffle arrays randomly.
This is basically the same as identifyAlbumsInPlaylist, except it uses the artistId. We'll use this for playing the discography of artists profiled in . Once again we could probably refactor these two functions together.
This is a tricky one. Essentially we're taking a large array of tracks in a playlist and converting it from a cohesive array to an object with smaller arrays with the same albumId. Those short arrays are albums, so then within each album we sort tracks by their track number on the album. Later we can recombine them.
Here's an example of what the effect of convertPlaylistToObjectByProperty looks like:Cool. Here's a quick one:
This is a great helper function to sort and array of objects based on a specified property of the objects within it when you feed it as a parameter to array.sort like we did in convertPlaylistToPbjectByProperty . I unabashedly lifted this from Stack Overflow, and owe Ege Ozcan a debt of gratitude!
Look at that export statement! Look at how readable that is! This function is called from the Play Now button in when a user chooses to shuffle a playlist by album WITHOUT adding missing tracks. It is an async function, and we're really benefiting now from abstracting out our lower-level operations.
We start by receiving the state from and pull out the name and id of the playlist chosen by the user. From there we:
get an array of tracks in the playlist
get an array albums in the playlist, which has already been shuffled
use convertPlaylistToObjectByProperty to sort the big array of tracks into an object with smaller arrays sorted by album
recombine the smaller (now sorted) track arrays back into a big array of tracks, but now grouped by album
flatten that recombined array so now it's a simple playlist again
pass that flat, recombined array to the createPlaylist function
I know we haven't tackled createPlaylist yet - saving that for last.
This one is very similar to our last function, but byAlbumWithDiscography has different logic in the middle. Once we have the shuffled array of albums from the user's playlist we throw the actual playlist away. Instead we loop through the array of albumIds and do an API call to get the tracks for each one, which we then combine into one big playlist.
There's one slightly unusual code pattern in there:In ES7 Javascript added the async/await keywords, but under the hood it's still using Promises. When we make an asynchronous API call within an array.map function we actually return an array of unfulfilled promises, not the result of those promises. Confusingly, even though it is full of unfulfilled promises, the array itself is created synchronously which means that awaiting promiseArrayOfTracksFromAlbum will not work. Instead, we call await Promise.all(promiseArrayOfTracksFromAlbum), which creates a new promise that resolves once all the promises in the array are resolved.
After Promise.all resolves we have an array of arrays of tracks, so we flatten it just like before and pass the result to createPlaylist.This is the function we use to create a playlist of the entire discography of the artist displayed in . It is almost exactly the same as byAlbumWithDiscography except instead of getting the array of albumIds from a playlist we get it by asking the SpotifyAPI for albums associated with the artist. While we're at it, let's go ahead and wire up this function with the Play Discography button in :
The final spotifyFunction - createPlaylist
Here it is ladies and gents - the last function in spotofyFunctions.js and the last code of the project! Ready to create a playlist and play it?
Well, Spotify's API doesn't make it easy on us. Even though we have the seemingly handy spotifyApi.createPlaylist and a spotifyApi.play functions, they come with some conditions:
We have to get the UserId before we can create a playlist
We have to create a blank playlist first, then add tracks to it.
We can't add more than 100 tracks at a time, so if we have more than 100 we need to call spotifyApi.addTracksToPlaylist multiple times.
We can only play the playlist if the user has an "active device" but it's not clear what an "active device" really means.No matter! That's why we have this custom createPlaylist function to handle the complexity. The logic of the function basically follows the challenges we just listed. chunk is a great Lodash library that splits a large array into smaller arrays. Also note that we have to use the Promise.all construction again for adding tracks because it's an async operation inside of an array.map.
Whoops - Lessons from real life - API setback
I know, you were so excited to be done, and I was too. But Spotify had other plans. Long story short between when I started this project and when the blogs were publish Spotify announced changes to their API and then reneged on those changes, thereby breaking our app! All they did was change the endpoint for creating a playlist, but that means that our spotofy-web-api-js library doesn't work! If I were a better citizen I'd do a pull request on the library to fix it, but the quick fix is to write our out temporary ajax API call for creating playlists for now instead of the library. Once the library is updated we'll change it back.
I didn't copy the whole file to the gist since we're only changing a few things:- Now that we need our access token outside of the spotify-web-api-js library we have to add a global variable to hold it, as well as update setAccessToken
- We need to call our tempCreatePlayist function inside our createPlaylist function that we wrote above.
- We need to make our temporary function, which is just an ajax call to the endpoint we need.
It Works!
Check it out - you should now be able to login to Spotify, choose among your playlists, and shuffle by album. You can also add related discography, as well as generate by-album discography for Kacey Musgraves, Ryan Bingham and Ted Leo (which I strongly recommend listening to in their entirety) or add homages to your own favorite artists!
The buttons don't do anything visual when they are clicked, but if you have your phone open to your playlists page next to you it should start playing after a few seconds, otherwise check your playlists to see the new album shuffled method and start listening old school style!Deploying the create-react-app to production
Last step, should you so desire, is to deploy your app so you can use it on the wild internet instead of just with your local development environment. This is not difficult, but the process isn't well documented when using create-react-app like we are.
One of the many functions that create-react-app takes care of under the hood for us in starting and running a development server with hot reloading, but for production the build script creates 1 Javascript, 1 CSS and one html file.
Because we just have those files, that means that we need to either put them on an existing server or spin up a simple Express server in Node to serve those files for us. That's the route we're going to take, so add a new file in your root directory called app.js. This is a DIFFERENT FILE than App.js in your components folder, and we'll write up a simple server to serve those build files:
Now that we have a server to host our build files we need a script to use it. Open up your package.json and let's do that.
create-react-app already has a nice build script for us, so for our productionstart script all we need to do is run that build script and then start the server. Notice we also added express as an explicit dependency. It shouldn't be necessary since create-react-app already uses it on the back end, but nice to be tidy.
The last file we need to add is Procfile (capitalized, no dot) which contains instructions for the deployment service (originally Heroku, but we'll use Cosmic' service). The Procfile will tell the service to spin up a single web node and to run our productionstart script.
So close we can taste it! Cosmic makes it extremely easy to deploy straight from your Github repo (you have been uploading your git commits, right?). All you have to do is go back to you Cosmic Dashboard and there is a Web Deployment option underneath settings. From there you simply click the blue Deploy to Cosmic button. It will take a few minutes to get set up and send you an email when you production Old School Shuffle is live.
The very, very, very last thing now is that we need to set our environmental variables within our Cosmic deployment to mirror our .env file from development. Protip - don't forget to click the Set Environment Variables button, and then give it a few minutes to update.
Final touches
Now that our app actually works I love it a little more, so I classed up the CSS to give it a little more pizzaz. These minor changes aren't shown in the github gists but they are reflected in the repo.
That's all folks!
And now enjoy the fruits of your labor! Thanks for joining me, and I hope you enjoyed it and learned something. This is my inaugural coding tutorial so please leave a comment if I missed anything... or if you have any comments!