How to Build a Podcast Player With React Js Tailwind Css and Apple Podcast API Part 2

ยท

11 min read

Building the homepage

Now we would focus on the homepage.

Before tackling the homepage, you need to provide the dependencies and files that the homepage needs because the homepage will be the first time we interact with Apples' API.

The following will be done in this section:

  • Install Axios
  • Create a loading spinner
  • Spin up your reverse proxy server
  • Create URL constants in a single file to efficiently manage our URLs

Other parts of the tutorial

๐Ÿ“˜ How to Build a Podcast Player With React Js part 1

๐Ÿ“˜ How to Build a Podcast Player With React Js part 2

๐Ÿ“˜ How to Build a Podcast Player With React Js part 3

๐Ÿ“˜ How to Build a Podcast Player With React Js part 4

๐Ÿ“˜ How to Build a Podcast Player With React Js part 5

Install axios

From the root of your project. Run:

yarn add Axios

Create loading spinner

Visit this site to get loading elements. You can choose anyone.

Create a file called Spinner within the containers folder. Then create two files in the Spinner folder named Loading.css and Loading.jsx.

Click on any loader you like from the site. Then, a modal shows you the HTML and CSS for that loader.

You can copy mine.

๐Ÿ”— You can visit GitHub and copy my loading files for the CSS and JavaScript.

Spin up your reverse proxy spinner

๐Ÿ’ก You can skip this step if it is confusing and use mine, then return to it when you complete the tutorial.

A reverse proxy is a server that sits in front of web servers and forwards client (e.g., a web browser) requests to those web servers. They are implemented to help increase security, performance, and reliability.

CORS Anywhere provided by Rob W will be used.

CORS Anywhere is a NodeJS proxy that adds CORS headers to the proxied request. I noticed many CORS issues when I deployed the website; namely, the Apple API server did not set the response header.

CORS Anywhere will prevent this. To ensure that the application doesn't crash when deployed.

๐Ÿ’ก You can use mine by just using my code. But if so many users are using the app. It might hit the limitations set by Heroku.

Heroku will be used to host our own CORS Anywhere proxy server.

Go to a folder where you want to host the code from your command line.

Then enter the following code in your command prompt or terminal and run them one after another.

git clone https://github.com/Rob--W/cors-anywhere.git
cd cors-anywhere/
npm install
Heroku create
git push Heroku master

It might prompt you to log in to Heroku from the command prompt. Complete that so that the Heroku CLI will have access to your account.

๐Ÿ’ก I will assume that you are still using mine for the rest of this tutorial. But I will advise that you build yours if you plan to host the application and showcase it to your friends or employers. So you can be sure that it is always online.

The reverse proxy's URL is prefixed before the API URL. So when the joined URL is requested. The reverse proxy server adds a header to the request before sending it off to the Apples' servers. I will provide more details in the next section.

Create URL constants in a single file to efficiently manage our URLs

In the src folder, create a new folder called utils and add consts.js in the utils folder.

Then copy the code below into the consts.js file.

const REVERSEPROXY_URL = 'https://secret-beyond-79263.herokuapp.com/' // Custom reverse proxy
const APPLE_PODCAST_URL = 'https://itunes.apple.com/'

export const BASE_URL = String(REVERSEPROXY_URL + APPLE_PODCAST_URL)

const HOME_SCREEN_PODCAST_COLLECTION_IDS = '278981407,863897795,1191775648,582272991,1200361736,1322200189,1379959217,998568017,1081244497,1062418176,1334878780,316045799,480486345,265307784,643055307,1057255460,1077418457,268213039,1258635512,169078375'

export const HOMESCREEN_API_URL = String(`${BASE_URL}lookup?id=${HOME_SCREEN_PODCAST_COLLECTION_IDS}`)

If you created your reverse proxy, replace the https://secret-beyond-79263.herokuapp.com/ with the URL generated by Heroku.

The reverse proxy is hosted at https://secret-beyond-79263.herokuapp.com/, it is saved in the REVERSEPROXY_URL constant.

To form the base URL for all API calls in this application, add the REVERSEPROXY_URL to the APPLE_PODCAST_URL with the reverse proxy URL coming first. The result will look like this: https://secret-beyond-79263.herokuapp.com/https://itunes.apple.com/{{-other parts of URL-}}. This means that any request is first sent to the reverse proxy, which attaches the header to the request before sending just the ..https://itunes.apple.com/..... part to Apple's servers for processing.

The HOME_SCREEN_PODCAST_COLLECTION_IDS constant is just a list of podcast collectionIds that will be displayed on the homepage.

A new constant is created and exported in the last line - HOMESCREEN_API_URL. The lookup?id= is part of the Apple API specification to search for something. For example, you can find more details about the Apple Podcast search API here.

Now that you have these requirements. You can move to the next task; the homepage, represented by the HomeScreen component.

You need a child component called HomePodcastSection to display each category of podcasts on the homepage. First, create a new file for this component.

Create a new file called HomePodcastSection.jsx in the components folder.

Copy the code below into the HomePodcastSection.jsx:

import React from 'react'

const HomePodcastSection = (props) => {

    const { header, podcasts, history } = props

    const handleClick = (collectionId) => {
        history.push(`podcast/${collectionId}`)
    }

    return (
        <>
            <div>
                <h1 className="text-left text-gray-100 text-2xl py-2 sm:pt-10 font-bold ">
                    {header}
                </h1>
            </div>
            <div className="flex flex-wrap flex-row">
                {
                    podcasts.map(podcast => (
                        <div
                            className="xl:w-1/5 md:w-1/3 sm:w-1/3 w-1/3 px-1 py-2"
                            key={podcast.collectionName}
                        >
                            <div onClick={() => handleClick(podcast.collectionId)}>
                                <div className="p-3 bg-gray-900 hover:bg-gray-800 cursor-pointer rounded-lg">
                                    <img className="rounded-lg w-full object-contain mb-1" 
                                      src={podcast.artworkUrl600} alt="content" />

                                    <div className="min-h-full h-14">
                                        <h2 className="text-left mt-2 home-screen-truncate-collection-name 
                                            text-sm text-white font-medium title-font">
                                            {podcast.collectionName}
                                        </h2>
                                        <p className="hidden md:block text-left pt-1 text-gray-400 text-xs">
                                          {podcast.artistName}
                                        </p>
                                        <p className="block md:hidden text-left pt-1 text-gray-400 text-xs">
                                          { podcast.artistName.length >= 30 
                                            ? 
                                            podcast.artistName.slice(0, 30) + '...' 
                                            : 
                                            podcast.artistName.slice(0, 30) }
                                        </p>
                                    </div>
                                </div>
                            </div>
                        </div>
                    ))
                }
            </div>
        </>
    )
}

export default HomePodcastSection

In the code in this file. Some data is passed to it as props. This component is a child component of HomeScreen. Therefore HomeScreen passes some data to it to loop through and display.

In this code - const { header, podcasts, history } = props destructures (read more about destructuring) the props to get the properties inside. The header and podcasts represent the header to be displayed and a list of podcasts to be looped through and displayed, respectively.

The history object was also passed from the parent component HomeScreen. It works with the router to change the URL of the app. In this case, a handleClick function that accepts a collectionId parameter, is used to identify a podcast by Apple. And then pushes it to one of the routes defined earlier in MainSection.jsx.

That is:

history.push(`podcast/${collectionId}`)

maps to this route

<Route exact path="/podcast/:collectionId" render={(props) => (<PodcastDetailsScreen {...props} />)} />

defined in the MainSection.jsx file.

To recap the last point, the handleClick function, when called, passes a collectionId to the /podcast/ route, which prompts the view to change from the HomeScreen the user is currently viewing to the PodcastDetailsScreen.

There are two significant segments in the component return statement, which outputs to the browser. The part that displays the destructured header and the part that loops through the podcasts with map.

Also in HomePodcastSection.jsx the home-screen-truncate-collection-name CSS class is a custom class to truncate any collectionName or podcast name that is too long and append ... at the end. So that all podcast elements on the webpage will have equal height.

While

{ 
 podcast.artistName.length >= 30 
 ? 
 podcast.artistName.slice(0, 30) + '...' 
 : 
 podcast.artistName
}

Checks the length of the artistName which is the podcast's description. If greater than 30 characters, it trims it and appends .... If it is not up to 30 characters, print it to the screen as-is.

With the HomePodcastSection complete. The HomeScreen component can now be updated.

Copy the code below. And overwrite the entire code in HomeScreen.jsx in the screens folder.

import React, { useEffect, useState } from 'react'
import axios from 'axios'

import { HOMESCREEN_API_URL } from '../utils/consts'

import HomePodcastSection from '../components/HomePodcastSection'
import Loading from '../containers/Spinner/Loading'

function HomeScreen(props) {

  const [podcasts, setPodcasts] = useState()

  useEffect(() => {
    const fetchAPI = async () => {
      getPodcasts()
        .then(data => {
          setPodcasts(data)
        })
        .catch(err => console.log(err))
    };
    fetchAPI();
  }, []);

  let popularPodcasts, crimePodcasts, comedyPodcasts, politicsPodcasts

  if (podcasts) {
    // The alternative to this will be to create 4 separate API calls, which is wasteful
    popularPodcasts = podcasts.slice(0, 5)
    crimePodcasts = podcasts.slice(5, 10)
    comedyPodcasts = podcasts.slice(10, 15)
    politicsPodcasts = podcasts.slice(15, 20)
  }

  const { history } = props

  return (
    <>
      {
        podcasts ?
          <>
            <section className="container px-5 mx-auto">
              <HomePodcastSection header={'Popular podcasts'} podcasts={popularPodcasts} history={history} />
              <HomePodcastSection header={'Top crime podcasts'} podcasts={crimePodcasts} history={history} />
              <HomePodcastSection header={'Top comedy podcasts'} podcasts={comedyPodcasts} history={history} />
              <HomePodcastSection header={'Top politics podcasts'} podcasts={politicsPodcasts} history={history} />
            </section>
          </>
          :
          <>
            <Loading />
          </>
      }
    </>
  )
}

export default HomeScreen

const getPodcasts = async () => {
  const response = await axios.get(HOMESCREEN_API_URL)
  return response.data.results
}

You should be familiar with all the import statements because I have talked about them previously except { useEffect, useState }, imported from React, at the top of the file. These are React Hooks that will help with API calls.

useState is a Hook that allows local state in functional components.

useEffect is a Hook that manages side-effects like fetch requests. It is used because it's terrible to put side-effect code directly in your components.

This code

const [podcasts, setPodcasts] = useState()

creates a variable named podcasts and a function -setPodcasts - to set the value of podcasts. Since in our useState there's is nothing in the parenthesis. It means podcasts is initialized as undefined. If the code was useState('') or useState({}) or useState({}), that means podcast was initialized with an empty string or empty object or empty array respectively.

To change the value of podcasts in the application at any time, call setPodcasts(newValue). That is how useState Hook works.

For example, to change the value of podcasts to the value const boy = 'Johnny' all you have to do is write setPodcasts(boy) or more clearly:

// 'useState example'
const [podcasts, setPodcasts] = useState()
console.log(podcasts) //-> output -> undefined
const names = ['John', 'Regina']
setPodcasts(names)
console.log(podcasts) //-> output -> ['John', 'Regina']

You can see that podcast wasn't assigned to names directly.

Moving on to the next piece of code:

useEffect(() => {
    const fetchAPI = async () => {
      getPodcasts()
        .then(data => {
          setPodcasts(data)
        })
        .catch(err => console.log(err))
    };
    fetchAPI();
}, []);

// Note that the code below is gotten from outside the component
// At the bottom of the 'HomeScreen' file
const getPodcasts = async () => {
  const response = await axios.get(HOMESCREEN_API_URL)
  return response.data.results
}

This code used the two hooks introduced earlier. The useEffect calls the getPodcasts function, which calls the API using the URL constant - HOMESCREEN_API_URL - defined in const.js and imported here. The function returns a list of podcasts.

Then the setPodcasts(data) sets podcasts to the value - data that was returned from the getPodcasts function.

Next

let popularPodcasts, crimePodcasts, comedyPodcasts, politicsPodcasts

if (podcasts) {
  // The alternative to this will be to create 4 separate API calls, which is wasteful
  popularPodcasts = podcasts.slice(0, 5)
  crimePodcasts = podcasts.slice(5, 10)
  comedyPodcasts = podcasts.slice(10, 15)
  politicsPodcasts = podcasts.slice(15, 20)
}

The code above initializes the four variables.

And checks if podcasts is set. Without the if statement, when you try to slice the podcasts as seen above, it will lead to an error because podcasts is initialized as undefined since you don't know whether the API call will be successful.

Next, the different ranges of podcasts are assigned to multiple variables. You can assign the different ranges of podcasts into different variables because you know what to expect from the API. Remember in the consts file; and we assigned HOMESCREEN_API_URL to a list of collectionIds. So the response from the API was organized in the same way we sent the request.

I chose this method to show different podcast categories because the homepage is a list of podcasts and four podcasts in 4 categories. One alternative would have been to make multiple calls to the API for the different categories. But that method is an inefficient use of API resources.

const { history } = props

The code above destructures history from props.

Then finally

<>
  {
    podcasts ?
      <>
        <section className="container px-5 mx-auto">
          <HomePodcastSection header={'Popular podcasts'} podcasts={popularPodcasts} history={history} />
          <HomePodcastSection header={'Top crime podcasts'} podcasts={crimePodcasts} history={history} />
          <HomePodcastSection header={'Top comedy podcasts'} podcasts={comedyPodcasts} history={history} />
          <HomePodcastSection header={'Top politics podcasts'} podcasts={politicsPodcasts} history={history} />
        </section>
      </>
      :
      <>
        <Loading />
      </>
  }
</>

For the code above.

podcasts ? checks whether podcasts is set. Remember, it was initially set to undefined.

If podcasts is set, it displays the section container, which calls each of the children component HomePodcastSection and passes them props. The header to display, the podcasts to loop through and display each podcast, and the history.

If podcast is not set, that is undefined. It displays the loading spinner with <loading />.

๐Ÿ’ก Checkpoint 2

If you have followed correctly. When you run yarn start. You should see a homepage with a list of podcasts. But when you click on them, they don't show any details. But nothing breaks in the application.

๐Ÿ”— If you encounter any errors, you can find the source code for this first part on GitHub. In addition, you can crosscheck with your work to see where you missed something.

See a live version of the application so far. Click on 'Open Sandbox' to see the complete code. Because the SideBar is not showing in the version below. Because the <iframe> is not wide enough to simulate a wider width screen.

Other parts of the tutorial

๐Ÿ“˜ How to Build a Podcast Player With React Js part 1

๐Ÿ“˜ How to Build a Podcast Player With React Js part 2

๐Ÿ“˜ How to Build a Podcast Player With React Js part 3

๐Ÿ“˜ How to Build a Podcast Player With React Js part 4

๐Ÿ“˜ How to Build a Podcast Player With React Js part 5

ย