I'm learning React by making a Spotify clone and for now, what I'm trying to do is to show Spotify sections such as "last played songs"
, "top artists"
and "top songs"
through a component called Body.js.
I get the data from a Spotify official API library created by jmperez in a useEffect hook in the App.js component. Once I get the data from the API, I store it in an object called initialState in a file called reducer.js
.
This reducer.js
file contains the initial state and the reducer function for a custom hook called useDataLayer.js
which is a useContext hook that passes as value a useReducer to all the branches of my program. This way what I do is update the initialState from App.js and access this object through the useDataLayer hook
in the different branches of my program (among them the Body component).
The problem I am having now is that it is not rendering the three sections mentioned before in Spotify, but only shows me one which is the "top songs". The weird thing is that for a second it does render the other components as if it was getting the data and rendering but then it updates and they disappear. Please if someone can help me with this problem and explain to me why this happens it would be great.
App.js code
import React, {useState, useEffect} from 'react';
import Login from './components/Login';
import Player from './components/Player';
import { getTokenFromResponse } from './spotify';
import './styles/App.scss';
import SpotifyWebApi from "spotify-web-api-js";
import { library } from '@fortawesome/fontawesome-svg-core';
import { fas } from '@fortawesome/free-solid-svg-icons';
import { fab } from '@fortawesome/free-brands-svg-icons';
import { useDataLayer } from './components/Hooks/useDataLayer';
library.add(fas, fab);
//spotify library instance
const spotify = new SpotifyWebApi();
function App() {
const [token, setToken] = useState(null);
const [{ user }, dispatch] = useDataLayer();
// where I get the necessary data from the api
useEffect(() => {
// function to get access token
let accessToken = getTokenFromResponse();
window.location.hash = '';
if(accessToken){
spotify.setAccessToken(accessToken);
setToken(accessToken);
//FROM HERE I GET THE DATA I NEED
// here I get the data of my user
spotify.getMe().then((data) =>{
dispatch({
type: "GET_USER",
user: date
})
});
spotify.getUserPlaylists().then((data) => {
dispatch({
type: "GET_PLAYLISTS",
playlists: dates
})
});
spotify.getMyTopTracks({limit: 4}).then((data) => {
dispatch({
type: "GET_TOP_TRACKS",
top_tracks:data,
})
});
spotify.getMyRecentlyPlayedTracks({limit: 4}).then((data) => {
dispatch({
type: "RECENTLY_PLAYED",
recently_played: date,
})
});
spotify.getMyTopArtists({limit: 4}).then((data) => {
dispatch({
type: "GET_TOP_ARTISTS",
top_artists: date,
})
});
}
}, [token])
//if the token is valid enter Player.js where Body.js is inside and if not return to the login component
return (
<div className="App">
{ token ? <Player spotify= {spotify} /> : <Login />}
</div>
);
}
export defaultApp;
Body.js code
import React from 'react'
import '../styles/Body.scss'
import { useDataLayer } from "./Hooks/useDataLayer.js";
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
function Body({ spotify }) {
//get the properties of the necessary data that I want to display in this component with useDataLayer
const [{ spotify_recommendations, recently_played, top_tracks, top_artists }, dispatch] = useDataLayer();
return (
<div className= "main-body">
<div className= "body-option">
<span className= "see-more">See All</span>
<FontAwesomeIcon icon={['fas', 'arrow-right']} />
<div>
{
//to show the image and info of the track
recently_played?.items.map((item, index) =>{
return (
<div className= "track" key= {index}>
<img src= {item.track.album.images[1].url} alt= "recently played track"></img>
<div className= "track-data">
<h3>{item.track.name}</h3>
<p>{item.track.artists.map(artist => artist.name).join(", ")}</p>
</div>
</div>
)
})
}
</div>
</div>
<div className= "body-option">
<span className= "see-more">See All</span>
<FontAwesomeIcon icon={['fas', 'arrow-right']} />
<div>
{
//to show the image and info of the track
top_tracks?.items.map((topArtist, index) => {
return (
<div className= "track" key= {index}>
<img src= {topArtist.album.images[1].url} alt= "recently played track"></img>
<div className= "track-data">
<h3>{topArtist.name}</h3>
<p>{topArtist.artists.map(artist => artist.name).join(", ")}</p>
</div>
</div>
)
})
}
</div>
</div>
<div className= "body-option">
<span className= "see-more">See All</span>
<FontAwesomeIcon icon={['fas', 'arrow-right']} />
<div>
{
//to show the image and info of the artist
top_artists?.items.map((topTrack, index) => {
return (
<div className= "track" key= {index}>
<img src= {topTrack.images[1].url} alt= "recently played track"></img>
<div className= "track-data">
<h3>{topTrack.name}</h3>
<p>{topTrack.genres.join(", ")}</p>
</div>
</div>
)
})
}
</div>
</div>
</div>
)
}
export default Body
Code of my custom hook useDataLayer.js
import React, {useContext, createContext, useReducer} from 'react'
let DataContext = createContext();
export function DataLayer({reducer, initialState, children}) {
return (
<DataContext.Provider value= {useReducer(reducer, initialState)}>
{children}
</DataContext.Provider>
)
}
export let useDataLayer = () => useContext(DataContext);
SideBar.js: the component where i display the user's playlist
import React from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { useDataLayer } from './Hooks/useDataLayer.js';
import '../styles/SideBar.scss';
function SideBar({ spotify }){
const [{ playlists }, dispatch] = useDataLayer();
return (
<div className= "side-bar">
<div className= "side-bar-options">
<div className= "spotify-logo">
<FontAwesomeIcon icon={['fab', 'spotify']} size= "3x"/>
<h1 className= "spotify-title">Spotify</h1>
</div>
<a className= "option" href= "./Player.js">
<FontAwesomeIcon icon={['fas', 'home']} />
<p className= "option-title" >Inicio</p>
</a>
<a className= "option" href= "./Player.js">
<FontAwesomeIcon icon={['fas', 'search']} />
<p className= "option-title">Buscar</p>
</a>
<a className= "option" href= "./Player.js">
<FontAwesomeIcon icon={['fas', 'headphones']} />
<p className= "option-title" >Tu Biblioteca</p>
</a>
</div>
<p className= "playlist-title">PLAYLISTS</p>
<div className= "playlists">
{
playlists?.items?.map(
(list, index) =>
<p className="playlists-option"
key={index}
>
{list.name}
</p>
)
}
</div>
</div>
)
}
export default SideBar;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Player.js
import React from 'react';
import "../styles/Player.scss";
import SideBar from "./SideBar.js";
import Body from "./Body.js";
function Player({spotify}) {
return (
<div className= "player-container">
<div className= "player_body">
<SideBar spotify= {spotify} />
<Body spotify= {spotify} />
</div>
</div>
)
}
export default Player;
spotify.js: the code where i get the token from the URL
const authEndpoint = "https://accounts.spotify.com/authorize";
const clientId = '84c134b175474ddabeef0e0b3f9cb389'
const redirectUri = 'http://localhost:3000/'
const scopes = [
"user-read-currently-playing",
"user-read-recently-played",
"user-read-playback-state",
"user-top-read",
"user-modify-playback-state",
"user-follow-modify",
"user-follow-read",
"playlist-modify-public",
"playlist-modify-private",
"playlist-read-private",
"playlist-read-collaborative"
];
//obtain acces token from url
export const getTokenFromResponse = () => {
let params = new URLSearchParams(window.location.hash.substring(1));
let token = params.get("access_token");
return token;
};
//acces url
const accessUrl = `${authEndpoint}?client_id=${clientId}&redirect_uri=${redirectUri}&scope=${scopes.join("%20")}&response_type=token&show_dialog=true`;
export default accessUrl;
Thank you very much for your time and attention.