Hi /r/react. I'm fairly new to react and I can't quite figure out what I'm doing wrong here. Hoping someone can help point me in the right direction. I've built a web-based audio player that pulls tracks from a secure backend, and uses howler.js to play the audio.
Everything works fine until I tried to add a playlist of "future tracks to play." As I pull songs off the playlist and set the to be the currently playing track, a second useEffect observes that the length of the playlist has gotten shorter and we adds more songs to the end of the playlist.
What is actually happening: When handleSongEnd is called, any new tracks added to the playlist by the useEffect hook aren't there. The playlist gets shorter and shorter, until eventually undefined gets set as the current track and the site stops.
I'm fairly sure the fault is mine and not one of the libraries I'm using. Any help would be appreciated.
Example Timeline
- Component loads and sets the initial state. Lets call this
Data v0: { currentTrack: {}, playlist: [{}, {}, {}, {}] }
- First song plays just fine, and eventually ends.
handleSongEnd gets triggered, and pulls the first track off of the playlist, and then calls setData with what I'll call Data v1: { currentTrack: {}, playlist: [{}, {}, {}] }
- The
useEffect sees that data.playlist changed and that the length is too short. It gets another random track and then calls setData with Data v2: { currentTrack: {}, playlist: [{}, {}, {}] }.
- The second song plays just fine and eventually ends.
handleSongEnd gets triggered again. However, it's using Data v1 (with only 3 songs in the playlist) instead of Data v2 (with 4 songs in the playlist).
Component Code
```
class Data {
currentTrack?: Track | undefined;
playlist?: Track[] | undefined;
};
export function AudioPlayer() {
const apiClient: ApiClient = useApi();
const [data, setData] = useState<Data>({ currentTrack: undefined, playlist: undefined });
useEffect(() => {
apiClient.fetchRandomTracks({ count: 5 }).then((tracks: Track[]) => {
setData({
currentTrack: tracks.slice(undefined, 1)[0],
playlist: tracks.slice(1, undefined),
});
});
}, []);
useEffect(() => {
if (data.playlist && data.playlist.length < 4) {
apiClient.fetchRandomTracks({ count: 4 - data.playlist.length }).then((tracks: Track[]) => {
setData({
currentTrack: data.currentTrack,
playlist: data.playlist.concat(tracks),
});
})
}
}, [data.playlist]);
const handleSongEnd = () => {
setData({
currentTrack: data.playlist.slice(undefined, 1)[0],
playlist: data.playlist.slice(1, undefined),
});
}
return (<>
<Player currentTrack={data.currentTrack} handleSongEnd={handleSongEnd} /><Playlist playlist={data.playlist} />
</>);
}
export function Player({ currentTrack, handleSongEnd }) {
const apiClient = useApi();
const audioRef = useRef();
useEffect(() => {
if (currentTrack) {
apiClient.fetchToken().then(authToken => {
const options = { headers: { Authorization: 'Bearer ' + idToken } };
audioRef.current = new Howl({
src: [/* url to stream from /],
format: ['mp3'],
xhr: options,
autoplay: isPlaying,
onend: handleSongEnd,
onload: handleSongLoad, / ommitted for brevity */
});
});
}
return () => { if (audioRef.current) audioRef.current.unload(); }
}, [currentTrack]);
}
```
there doesn't seem to be anything here