I'm fetching 3 different endpoints from my backend. My goal is to display all the data. I need to pass this data to the correct components, however I am using dynamic routes.
My error is that my data comes to artists.js as an empty array.
I have tried:
Using useNavigate to pass the data link to previous SOF question, user suggested redux toolkit
Using Link to pass the data.
Using redux toolkit to pass the data. I am new to Redux, so it's possible I have done something wrong in my code.
I believe it's a timing, rendering cycle error. However I am not sure how to fix it. When I put in some conditional rendering in the artists component, it showed me the correct data in the console logs on it's second render. But now I am not sure what to do from there. It is stuck with the page saying "loading". I tried mapping the data after the conditional rendering (where return <div>Loading...</div>;
is), but on page refresh, all the rendered data disappears from the page.
here is the code from top to bottom:
index.js
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { BrowserRouter } from "react-router-dom";
import { Provider } from "react-redux";
import store from "./redux/store";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
</React.StrictMode>
);
reportWebVitals();
app.js
import "./App.css";
import Genres from "./Components/Genres"
import TopTracks from "./Components/TopTracks"
import Results from "./Components/Results"
import { Routes, Route} from "react-router-dom"
function App() {
return (
<Routes>
<Route path='/' element={<Genres />} />
<Route path='/:genre' element={<Results />} />
<Route path='/:artist' element={<TopTracks />} />
</Routes>
);
}
export default App;
genres.js
import React, { useState, useEffect } from "react";
import { Link } from "react-router-dom";
import { useDispatch } from 'react-redux';
import { setData } from '../redux/spotifyDataSlice';
function Genres() {
const [genres, setGenres] = useState([]);
const dispatch = useDispatch();
useEffect(() => {
fetch("/api/genres/")
.then((response) => response.json())
.then((data) => setGenres(data.genres))
.catch((error) => console.log(error));
}, []);
function handleClick(genre) {
const query_params = {
genre: genre
};
Promise.all([
fetch("/api/artists/", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRFToken": "",
},
body: JSON.stringify(query_params),
}),
fetch("/api/playlists/", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRFToken": "",
},
body: JSON.stringify(query_params),
}),
fetch("/api/tracks/", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRFToken": "",
},
body: JSON.stringify(query_params),
})
])
.then((responses) => Promise.all(responses.map((response) => response.json())))
.then(([artists, playlists, tracks]) => {
dispatch(setData({ artists, playlists, tracks }));
})
.catch((error) => console.log(error));
}
return (
<div>
<div className="genre-list-container">
<ul className="genre-list">
{genres.map((genre) => (
<li className="genre" key={genre}>
<Link to={`/${genre}`} onClick={() => handleClick(genre)}>{genre}</Link>
</li>
))}
</ul>
</div>
</div>
);
}
export default Genres;
results.js
import React from "react";
import Artists from "./Artists";
import Playlists from "./Playlists";
import Tracks from "./Tracks";
function Results() {
return (
<div>
<Artists />
<Playlists />
<Tracks />
</div>
);
}
export default Results;
artists.js
import React, { useState } from "react";
import { useNavigate } from "react-router-dom";
import { useSelector } from 'react-redux';
function Artists() {
const navigate = useNavigate();
const artists = useSelector((state) => state.spotifyData.artists);
console.log(artists)
const [isLoading, setIsLoading] = useState(true);
if (isLoading) {
if (artists.length === 0) {
return <div>No artists found</div>;
} else {
return <div>Loading...</div>;
}
}
function handleClick(artist) {
fetch("/api/top_tracks/", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRFToken": "",
},
body: JSON.stringify({ artist_id: artist.id }),
})
.then((response) => response.json())
.then((data) => {
console.log(data);
navigate(`/${artist.name}`, { state: { data } });
});
}
return (
<div>
<div>Artists:</div>
{artists.map((artist) => (
<div key={artist.id}>
<img src={artist.image_url} alt={artist.name} />
<h1 onClick={() => handleClick(artist)}>{artist.name}</h1>
<p>Popularity: {artist.popularity}</p>
<p>Genres: {artist.genres}</p>
</div>
))}
</div>
);
}
export default Artists;
spotifyDataSlice.js:
import { createSlice } from '@reduxjs/toolkit';
const spotifyDataSlice = createSlice({
name: 'spotifyData',
initialState: {
artists: [],
playlists: [],
tracks: [],
},
reducers: {
setData(state, action) {
const { artists, playlists, tracks } = action.payload;
console.log("Artists:", artists);
console.log("Playlists:", playlists);
console.log("Tracks:", tracks);
state.artists = artists;
state.playlists = playlists;
state.tracks = tracks;
},
},
});
export const { setData } = spotifyDataSlice.actions;
export default spotifyDataSlice.reducer;
store.js:
import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit';
import spotifyDataReducer from './spotifyDataSlice';
const loggerMiddleware = store => next => action => {
console.log('Dispatching:', action);
const result = next(action);
console.log('Next State:', store.getState());
return result;
};
const store = configureStore({
reducer: {
spotifyData: spotifyDataReducer,
},
middleware: getDefaultMiddleware().concat(loggerMiddleware)
});
export default store;
edit: Here are some examples of my POST responses.
{artists: {…}, playlists: {…}, tracks: {…}}
artists:
artists: {total: 13, artists: Array(13)}
example of artists object:
{name: 'Aborted', id: '1XRhUgCyzIdeT8d9KMfeDR', image_url: 'https://i.scdn.co/image/ab6761610000e5eb457a41afcb20ec257fce22d4', popularity: 39, genres: Array(8)}