Develop and launch an enhanced Spotify 2.0 clone music app using React 18, Tailwind, Redux, and Rapid API Hub (Part 2)

This is the second guide of the series that explains how to create and launch a clone music app using React 18, Tailwind, Redux, and Rapid API Hub.

The first part of the guide focused on the React music player's file and folder structure, and it provided instructions on how to run the app on a local server and view its basic layout.

In this part of the guide, we will explore how to fetch music from the Rapid API Hub, and we'll discuss the overall route structure and development of the Discover.jsx page, which is a critical component of the app's user interface.

Before continuing, you can go ahead and sign up for Rapid API Hub to access thousands of APIs that we can use for our project.

Loading component...

Moving forward, we will return to the code by minimizing our terminal, accessing the source files, and beginning with the App.jsx file.

Exploring App.jsx file

First, we will discuss the file's structure, including a general route configuration. The first route listed is the Discover, and by holding the Ctrl or Cmd key and clicking on it, we will be directed to the Discover.jsx page, which functions as an independent page within the application.

js
import { useSelector } from 'react-redux';
import { Route, Routes } from 'react-router-dom';
import { Searchbar, Sidebar, MusicPlayer, TopPlay } from './components';
import { ArtistDetails, TopArtists, AroundYou, Discover, Search, SongDetails, TopCharts } from './pages';
const App = () => {
const { activeSong } = useSelector((state) => state.player);
return (
<div className="relative flex">
<Sidebar />
<div className="flex-1 flex flex-col bg-gradient-to-br from-black to-[#121286]">
<Searchbar />
<div className="px-6 h-[calc(100vh-72px)] overflow-y-scroll hide-scrollbar flex xl:flex-row flex-col-reverse">
<div className="flex-1 h-fit pb-40">
<Routes>
<Route path="/" element={<Discover />} />
<Route path="/top-artists" element={<TopArtists />} />
<Route path="/top-charts" element={<TopCharts />} />
<Route path="/around-you" element={<AroundYou />} />
<Route path="/artists/:id" element={<ArtistDetails />} />
<Route path="/songs/:songid" element={<SongDetails />} />
<Route path="/search/:searchTerm" element={<Search />} />
</Routes>
</div>
<div className="xl:sticky relative top-0 h-fit">
<TopPlay />
</div>
</div>
</div>
<MusicPlayer song={activeSong} />
</div>
);
}
export default App;

Creating Discover Page

Our next step is constructing the Discover page, consisting of the interface shown in the image below. This interface will be the final structure of our application.

Initially, we will create a div element for the discover icon, followed by a genre selector at the top. We will then retrieve all of the songs from an API and display them, along with hover effects and visually appealing artwork. Let's start this in the following steps.

Step 1

We will begin by opening our Discover.jsx file on one side of the screen and the local server at https://localhost:3000/ on the other side, allowing us to work simultaneously on both the code and the app's visual display.

To begin creating our Discover page, we will import several other components necessary for its implementation. These components include the Error component, the Loader component, and the SongCard component. Each of these components is simply a div that displays the name of the individual element. Importing these components is necessary, as we will require them to create our Discover page.

Additionally, we can retrieve the genres data from assets/constants by importing it into our Discover.jsx page. By clicking on the genres file while holding the Ctrl or Cmd key, we can observe that it is an array comprising of objects, with each object containing two key-value pairs. The title key holds a human-readable name for the genre, while the value key consists of an all-uppercase string with underscores.

Now that we have imported our necessary components and the genres data, we can create the return statement for our Discover.jsx page. We can also add a console.log(genres) statement to check if the genre data is being correctly retrieved.

js
import { Error, Loader, SongCard } from '../components';
import { genres } from '../assets/constants';
const Discover = () => {
console.log(genres);
return (
<div>
Discover
</div>
);
}
export default Discover;

Step 2

In this step, we will begin by creating the structure. We will start by adding a div element with a className of flex and flex-col for column layout. It's important to note that we are using Tailwind CSS for styling and have a Tailwind extension installed to help us visualize each property. You can find and install the Tailwind CSS Intelligence extension by searching for it in the extensions marketplace within Visual Studio Code.

Additionally, Tailwind has an official documentation website where you can find detailed explanations and examples for each class. You can access it at this link. Here is the updated code for our Discover page:

js
import { Error, Loader, SongCard } from '../components';
import { genres } from '../assets/constants';
const Discover = () => {
console.log(genres);
return (
<div className="flex flex-col">
Discover
</div>
);
}
export default Discover;

Step 3

In this step, we will create the Discover page by adding another div inside the parent div. This new div will have a className value of w-full for full width and flex justified-between items-center to align the items in a justified manner with space between them.

On small devices, this div will have a flex-row display property and a flex-column for larger devices. Additionally, we will add a margin-top property with a value of mt-four and mb-10 to set the margin at the top and bottom, respectively.

We will add a select component for the genre selection inside this' div'. However, we must also add an h2 element above the select component. We will create the h2 element with the text content of Discover and assign it some class names such as font-bold and text-3xl to make it more prominent.

Additionally, we will assign it text-white and text-left class names to change its color to white and align the text to the left.

js
import { Error, Loader, SongCard } from '../components';
import { genres } from '../assets/constants';
const Discover = () => {
console.log(genres);
return (
<div className="flex flex-col">
<div className="w-full flex justify-between items-center flex-row md:flex-col mt-4 mb-10">
<h2 className="font-bold text-3xl text-white text-left">Discover</h2>
<select>
{genres.map((genre) => (
<option key={genre.value} value={genre.value}>
{genre.title}
</option>
))}
</select>
</div>
</div>
);
};
export default Discover;

Step 4

In this step, we will hard code the title as Pop for now, but we will change it later. Our select element will have some basic properties that most input elements have, such as the onChange() property, which we can leave empty for now, and the value() property, which we will set to an empty string for the time being.

Next, we will add a className attribute to the select element, which will apply some styles, such as a black background color, gray text color, and rounded corners. The p-3 and text-sm classes will set the padding and font size of the element, respectively.

The outline-none class will remove the default outline around the element when it is focused. Additionally, the sm:mt-@ and mt-5 classes will add margin to the top of the element on different screen sizes.

js
import { Error, Loader, SongCard } from '../components';
import { genres } from '../assets/constants';
const Discover = () => {
console.log(genres);
const genreTitle = "Pop";
return (
<div className="flex flex-col">
<div className="w-full flex justify-between items-center flex-row md:flex-col mt-4 mb-10">
<h2 className="font-bold text-3xl text-white text-left">Discover</h2>
<select
onChange={() => {}}
value=""
className="bg-black text-gray-400 rounded p-3 text-sm outline-none sm:mt-5 mt-5"
>
{genres.map((genre) => (
<option key={genre.value} value={genre.value}>
{genre.title}
</option>
))}
</select>
</div>
</div>
);
};
export default Discover;

Step5

To implement the above, we can add the map() method to the select element and iterate over the genres array. We can create an option element for each genre with the value attribute set to the genre's value property and the text content set to the genre's title property. This will display all available genres as selectable options for the user.

js
import { Error, Loader, SongCard } from '../components';
import { genres } from '../assets/constants';
const Discover = () => {
console.log(genres);
const genreTitle = "Pop";
return (
<div className="flex flex-col">
<div className="w-full flex justify-between items-center flex-row md:flex-col mt-4 mb-10">
<h2 className="font-bold text-3xl text-white text-left">Discover</h2>
<select
onChange={() => {}}
value=""
className="p-3 text-sm bg-black text-gray-400 rounded outline-none sm:mt-5 mt-3"
>
{genres.map((genre) => (
<option key={genre.value} value={genre.value}>
{genre.title}
</option>
))}
</select>
</div>
</div>
);
};
export default Discover;

This is how our application looks like after this step:

Step 6

To create the wrapper for our songs, we can add another div element below the first one inside the return statement of the Discover component. This new div will serve as a container for the song cards that we will render.

We will assign a CSS class name flex flex-wrap to the new div. On small devices, we will set its alignment to sm:justify-start, but typically it should be centered using justify-center with a gap of gap-8.

We will then iterate over the songs fetched from the API and display them inside this div. However, for now, we will create a sample array of numbers ranging from 1 to 10, and consider each number as a song.

We need to include the SongCard component and provide it with some information. Firstly, we need to set the key prop of the SongCard component to be equal to song.key.

Additionally, we need to pass the entire song object to the SongCard component as a prop, with song being equal to song. Finally, we also need to provide the index of each song, which is available as the second parameter i of the map function.

jsx
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Error, Loader, SongCard } from '../components';
import { genres } from '../assets/constants';
const Discover = () => {
const genreTitle = `Pop`;
return (
<div className="flex flex-col">
<div className="w-full flex justify-between items-center sm:flex-row flex-col mt-4 mb-10">
<h2 className="font-bold text-3xl text-white text-left">Discover {genreTitle}</h2>
<select
onChange={() => {}}
value=""
className="bg-black text-gray-300 p-3 text-sm rounded-lg outline-none sm:mt-0 mt-5"
>
{genres.map((genre) => (
<option key={genre.value} value={genre.value}>{genre.title}</option>
))}
</select>
</div>
<div className="flex flex-wrap sm:justify-start justify-center gap-8">
{[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((song, i) => (
<SongCard key={song.key} song={song} i={i} />
))}
</div>
</div>
);
};
export default Discover;

That's all the information we need to pass at the moment. As a result, we will now see the SongCard repeated ten times.

The reason why the Discover component was started with is because it leads us to the most crucial functionalities of the application, which are fetching songs from the Shazam Core API and then styling and displaying them.

Wrap Up

In this guide, we've gone through the process of building a music Lyriks website using React, Tailwind CSS, and the Rapid API Hub.

We started by setting up the project and creating a layout for the homepage. Then, we added a dropdown menu to select music genres and displayed a list of demo songs using the SongCard component.

Along the way, we covered various topics such as React hooks, handling events, conditional rendering, and CSS styling with Tailwind. Stay tuned for the next part of the guide.