I'm building a web application that fetches mixes from my favorite radio stations on soundcloud with the soundcloud rapid API and displays them in a randomized order. I'm building this application using React.js and express.js for the backend. It's my first API project that i'm building without following a tutorial.
To reduce performance issues or cpu overload, i want the frontend to only fetch a certain amount of mixes at a time, in this case 5 and after the last of these five mixes have been in the intersectionObserver, 5 more should get fetched. The communication between the frontend and the backend works fine, the tracks get fetched by the frontend and displayed in the feed as well. My problem now is that, even though the backend fetches several mixes and the backend array doesn't have any duplicates (checked with a function called findDuplicates), the frontend displays duplicates and the loading behavior in general is a bit weird. It's hard to explain what it does, but it fetches certain mixes twice and then after these have been displayed some new mixes get shown and after a while it somehow repeats the first fetched again but without any logic or pattern (or i couldn't find one).
I've tweaked the numbers a bit but the behavior doesn't really change. Here's the frontend:
import React, { useState, useEffect, useRef } from "react";
import ReactPlayer from "react-player";
import "./Feed.css";
import faultradio from "../Home/SecondSection/Logos/radio-stations/fault-radio.png";
import kioskradio from "../Home/SecondSection/Logos/radio-stations/kiosk-radio.png";
import nts from "../Home/SecondSection/Logos/radio-stations/nts.png";
import thelotradio from "../Home/SecondSection/Logos/radio-stations/the-lot-radio.png";
import trnstnradio from "../Home/SecondSection/Logos/radio-stations/trnstn-radio.png";
const getLogoByUser = (user) => {
switch (user) {
case "faultradio":
return faultradio;
case "kioskradio":
return kioskradio;
case "nts-latest":
return nts;
case "thelotradio":
return thelotradio;
case "trnstnradio":
return trnstnradio;
default:
return null;
}
};
const Feed = () => {
const [usersData, setUsersData] = useState([]);
const [currentRadioStation, setCurrentRadioStation] = useState("");
const [offset, setOffset] = useState(0);
const elementsRef = useRef([]);
const limit = 5;
const lastTrackRef = useRef();
const fetchTracks = () => {
fetch(
`http://localhost:4000/api/soundcloud?offset=${offset}&limit=${limit}`
)
.then((response) => {
if (!response.ok)
throw new Error("Request failed with status " + response.status);
return response.json();
})
.then((data) => {
console.log(data);
setUsersData((oldData) => [...oldData, ...data.message]);
setOffset((oldOffset) => oldOffset + limit);
})
.catch((error) => {
console.error(error);
});
};
useEffect(() => {
fetchTracks();
}, []);
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
setCurrentRadioStation(entry.target.dataset.user);
}
});
},
{ threshold: 0.5 }
);
elementsRef.current.forEach((element) => {
if (element) {
observer.observe(element);
}
});
return () => {
elementsRef.current.forEach((element) => {
if (element) {
observer.unobserve(element);
}
});
};
}, [usersData]);
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
fetchTracks(); // Fetch more tracks when last track is in view
}
});
},
{ threshold: 0.5 }
);
if (lastTrackRef.current) {
observer.observe(lastTrackRef.current);
}
return () => {
if (lastTrackRef.current) {
observer.unobserve(lastTrackRef.current);
}
};
}, [usersData]);
return (
<div className="feed">
<h1>
Feed me <br />
new music
</h1>
<div className="rotate-div"></div>
<div className="img-container">
{usersData.length > 0 && (
<img
src={getLogoByUser(currentRadioStation)}
className="radio-station-img"
alt={currentRadioStation}
/>
)}
</div>
{usersData.map((data, index) => {
const isLastItem = index === usersData.length - 1;
return (
<div
className="feed-player-container"
data-user={data.user}
ref={(element) => {
elementsRef.current.push(element);
if (isLastItem) lastTrackRef.current = element;
}}
key={index}
>
<ReactPlayer url={data.track.url} className="react-player" />
</div>
);
})}
</div>
);
};
export default Feed;
I don't exactly know if the error is in the fetchTracks function or somewhere else, that's why i'm posting my whole component. No other component uses this api so i would assume it has to be here somewhere.
backend:
const express = require("express");
const cors = require("cors");
const fetch = require("node-fetch");
const app = express();
const PORT = process.env.PORT || 4000;
app.use(
cors({
origin: "http://localhost:3000",
methods: "GET, POST",
allowedHeaders: ["Content-Type", "Authorization"],
})
);
let dataCache = null;
let isFetching = false;
const fetchData = async () => {
try {
const profileURLs = [
"kioskradio",
"trnstnradio",
"thelotradio",
"nts-latest",
"faultradio",
];
const responses = await Promise.all(
profileURLs.map((profile_url) =>
fetch(
`https://soundcloud4.p.rapidapi.com/user/info?profile_url=https%3A%2F%2Fsoundcloud.com%2F${encodeURIComponent(
profile_url
)}`,
{
method: "GET",
headers: {
"X-RapidAPI-Key":
"nope",
"X-RapidAPI-Host": "soundcloud4.p.rapidapi.com",
},
}
).then((response) => {
if (!response.ok) throw new Error("Network response was not ok");
return response.json();
})
)
);
const arr = responses.flatMap((data) =>
data.tracks.map((track) => ({
user: data.username,
track,
}))
);
// function findDuplicates(array, property) {
// const duplicates = [];
// const values = new Set();
// for (let i = 0; i < array.length; i++) {
// const value = array[i][property];
// if (values.has(value)) {
// duplicates.push(array[i][property]);
// }
// values.add(value);
// }
// console.log(duplicates);
// }
// console.log("array: ", arr[0].track.title);
// findDuplicates(arr, "track.title");
shuffleArray(arr); // shuffle array
dataCache = arr;
// console.log("Data fetched:", arr.length);
// console.log("Data cache:", dataCache.length);
} catch (error) {
console.error(error);
} finally {
isFetching = false;
}
};
const fetchInterval = 60 * 60 * 500; // Fetch every 30 minutes
// Fetch data initially and start the interval
fetchData();
setInterval(() => {
if (!isFetching) {
fetchData();
}
}, fetchInterval);
// shuffle array function
function shuffleArray(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
}
app.get("/api/soundcloud", (req, res) => {
const offset = parseInt(req.query.offset) || 0;
const limit = parseInt(req.query.limit) || 10;
if (dataCache) {
const paginatedData = dataCache.slice(offset, offset + limit);
res.json({
message: paginatedData,
});
} else {
res.status(500).json({ error: "No data available" });
}
});
app.listen(PORT, () => {
console.log(`Server is listening on ${PORT}`);
});
thanks for your time and help!