I am making a playlist player using Soundcloud API and encounter a problem when clicking on next/previous buttons to change to the next song. The initial song is fetched successfully using useEffect
hook, but I don't know how to call this hook again when onClick()
method is executed within next/previous buttons.
Currently, the play/pause button works correctly, but the next/previous buttons only work under some specific sequence of clicks involving { play/pause/previous/next } buttons. That is expected given that my useEffect hooks seem erroneous.
[-------- Working JSFiddle here !!---------]
import react, { useState, useEffect } from 'react';
const SC = require('soundcloud');
SC.initialize({
client_id: '1dff55bf515582dc759594dac5ba46e9'
});
const playlist = [
913128502,
755552476,
604329399
]
const playIcon = 'https://www.pngfind.com/pngs/m/20-201074_play-button-png-image-background-
circle-transparent-png.png';
const pauseIcon = 'https://thumbs.dreamstime.com/z/pause-button-icon-transparent-background-
sign-flat-style-204042531.jpg';
const prevIcon = 'https://i.postimg.cc/xCry8pgt/prev2.png';
const nextIcon = 'https://i.postimg.cc/ncwmMwhB/next.png';
const musicFetch = (trackId) => {
return SC.stream('/tracks/' + trackId)
.then(track => {
return track;
})
}
const Playlist = () => {
const [playState, setPlayState] = useState(false);
const [track, setTrack] = useState<any>(null);
const [playlistIndex, setPlaylistIndex] = useState(0);
// use effect that fetches the initial song upong first render
useEffect(() => {
musicFetch(playlist[playlistIndex]).then((music) => {
music.setVolume(0.025); //increase if you don't hear background sound
setTrack(music);
});
}, [])
// use effect that should fetch new song upon clicking next or previous button
useEffect(() => {
musicFetch(playlist[playlistIndex]).then((music) => {
music.setVolume(0.2); //increase if you don't hear background sound
setTrack(music);
});
}, [setPlayState, playlistIndex])
const handleClick = async (direction) => {
direction === 'play' ? await enableMusic() : await changeMusic(direction);
}
const enableMusic = async () => {
if (track) {
setPlayState(!playState);
playState ? await track.pause() : await track.play();
}
}
const changeMusic = async (direction) => {
setPlaylistIndex(
direction === 'next' ? await playlistIndex+1 : await playlistIndex-1
);
}
return (
<div>
<img src={prevIcon}
onClick={() => { handleClick('prev') }}
height="100" width="100"/>
<img src={!playState ? playIcon : pauseIcon }
onClick={() => { handleClick('play') }}
height="100" width="100" >
</img>
<img src={nextIcon}
onClick={() => { handleClick('next') }}
height="100" width="100"/>
</div>
)
}
export default Playlist;
I have read about useEffect
hook conditional dependency array, and tried to set in my useEffect hook any combination involving setPlaylistindex
,playlistIndex
, setTrack
, but with no success. I have considered the useCallback
hook but that doesn't seem to be the correct solution.
I tried to find a way to dismount and mount, such that the empty dependency useEffect hook is called upon request, I was able to design a forceUpdate()
for that matter, but that didn't fix the problem either.
Does anyone know how I can fix this problem?
You need to wire the dependencies in your useEffect
hook correctly.
Have separate useEffect
hooks to capture play/pause and play list index state changes. You can add dependency for track
to the useEffect
hook that handles play/pause state changes and remove any other useEffect
hooks, including the one with empty dependency list.
React.useEffect(async () => {
const music = await musicFetch(playlist[playlistIndex]);
music.setVolume(0.2);
setTrack(music);
}, [playlistIndex]);
React.useEffect(async ()=>{
playState ? await track.play() : await track.pause();
}, [track, playState]);
You can then refactor enableMusic
method as follows
const enableMusic = async () => {
if (track) {
setPlayState(!playState);
}
}