Using the useEffect hook with a dependency array for the setInterval method - Build a 25 + 5 Clock

Scenario

This project duplicates an already completed ‘countdown timer’ project. Even though it has completed the project’s logic has been enhanced several times with the assistance of contributors from the FCC community ( both before & after the completion ) & I want to implement the existing logic properly with correct guidance. Currently, I am encountering difficulties with running a useEffect hook with dependencies. My previous version of this useEffect did not include a dependency array but now that one has been added, a few issues have arisen.

            i. the audio element can’t be accessed within the useEffect ( err: client not allowed )
            ii. the setInterval method within the useEffect hook contains method calls that are not functioning as intended.
            iii. couldn’t able to pass those methods within the dependency array of the useEffect hook properly ( they are also static functions ).

Logic implementation in summary

  • upon btn click an onClick evt triggers & call the ‘ManageSession’ method
  • after invoking the ‘ManageSession’ method, it executes based on the passed parameters & update the component’s state
  • on state changes, the useEffect comes into action & start a countdown
  • while the countdown runs, sub methods & a single state changer will be invoked ( there’s the problem ) based on conditions
  • upon a new btn click events, the countdown should stopped, paused & reset ( working )

Logic outcome so far

[ wrong but partially working logic => JS section : line 63 to 86 ]

@0x74h51N can u spare a minute of ur time & check my updated useEffect hook (line 63 to 86) & guide me how to restructure it propery? ~Thanks~

Hello there, great to see you still working on improvement and learning :+1:.

You can directly control your audRef inside your interval or useEffect, it shouldn’t give an error. Just make sure that the ref is mounted to an element and is not null.

  React.useEffect(() => {
    
    if(sessionState==="running" && !sessionPaused) { 
      const timerID = setInterval(() => {
        setSessionTime((prevTime) => {
          if(prevTime > 0) {
            return prevTime - 1;
          } else {
            handlePlayPause();
            ManageSession("change");
            return 0;
          }
        });
      },1000);
      setSessionTimer(timerID);
      return () => clearInterval(timerID);
    }
  },[sessionState,sessionTime,sessionPaused]);

You can directly control your audRef instead of using the handlePlayPause function. However, it looks clearer and more modular to do it inside that function.

But this function is a bit buggy:

  const handlePlayPause = () => {  
    if(audRef.current) {
      if(isPlaying) {
        audRef.current.pause();
      } else {
        audRef.current.play();
      }
      setIsPlaying(!isPlaying);
    }
  };

When sessionTime reaches zero inside the setSessionTimer callback, this function is triggered. Since isPlaying state is false, it calls audRef.current.play() and the isPlaying state changes outside the conditions. When the break time ends, audRef can’t start because isPlaying is still true and it gets set to false when break time reaches zero. This cycle continues, causing the alarm to play every other session.

You can manage this situation more easily without using state. For example, after playing audRef, you can use setTimeout to pause it with a 2-3 second delay. Resetting currentTime to zero after each pause is a simpler approach (although .wav files are often loop-friendly even without resetting). This way, the audio file can replay every time the interval reaches zero.

Also you can use your audRef in your reset condition too. :+1:

1 Like