Search code examples
reactjsajaxuse-effect

React changing song in playlist, useEffect hook dependency array


Problem Description

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.

JSFiddle (Wasn't able to insert Stackoverflow JS/HTML/CSS widjet)

[-------- Working JSFiddle here !!---------]

Playlist.tsx

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;

Things I have tried

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?


Solution

  • 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);
        }
     }