(React 18) 25 + 5 Clock Project Test Fail

Hello everyone,

I’m encountering an issue with passing the last test of the 25 + 5 clock project. Despite trying several approaches, I’m unable to pass the following test:

“The audio element with id of ‘beep’ must stop playing and be rewound to the beginning when the element with the id of ‘reset’ is clicked.”

When I test this functionality myself, it works as expected; the reset button successfully pauses the audio element. However, during the actual test, it doesn’t seem to work. I suspect the issue might be related to timeouts.

I attempted to address this by optimizing the code with updated useEffect dependencies, and I even tried separating the useEffect specifically for the reset dependency. However, the outcome remained unchanged.

Additionally, if I add more if statements or control/state management conditions, such as context, it starts to fail other tests. While attempting to make the code faster, it seems that the useEffect becomes overloaded. I’ve spent the entire day trying to pass all of the tests, but unfortunately, I haven’t been successful.

Could you please help me determine whether this is due to a mistake on my part or an issue related to React 18? I’ve looked into React 18 issues, and it seems that most have been resolved by the great developers, but I’m still unsure.

If you want to see the codePen live version, firstly apologies for the messy code :flushed:. It started with a different visual approach at the beginning, but it’s been growing bigger and bigger every day :sweat_smile: and I made a big challenge for my self… The relevant code for the app starts at line 677. To access the 25+5 clock, you should double-click on the shortcut; otherwise, it won’t render.

Also, when I set the interval to 1000ms, it sometimes causes some tests to fail. That’s why I set it to 999ms, but if this approach is not ethic, I can change although it might require changing the entire codebase :flushed:

Thank you…

const formatTime = (totalSeconds) => {
  const minutes = Math.floor(totalSeconds / 60);
  const seconds = totalSeconds % 60;
  const formattedMinutes = minutes < 10 ? `0${minutes}` : `${minutes}`;
  const formattedSeconds = seconds < 10 ? `0${seconds}` : `${seconds}`;
  return formattedMinutes+':'+formattedSeconds;
  };
const CountdownTimer = ({ initialSeconds, onComplete, start, reset }) => {
  const { volume } = useVolumeControl();
  const audioElement = useRef(null)
  const [seconds, setSeconds] = useState(initialSeconds);
  const [timeInterval, setTimeInterval] = useState(null);
  const [breakTimeout, setTimeoutState] = useState(null);

  const secondUpdater = () => {
    setSeconds((prevSeconds) => {
        if (prevSeconds > 0) {
          if (prevSeconds === 5) {
            audioElement.current.src = countdownAudioSrc;
            audioElement.current.play();
          }
          return prevSeconds - 1;
        } else {
          onComplete();
          return 0;
        }
      });
    }
  
  useEffect(()=>{setSeconds(initialSeconds)},[initialSeconds])
  
  useEffect(() => {
  clearInterval(timeInterval);
  clearTimeout(breakTimeout);
  audioElement.current.pause();
  audioElement.current.currentTime = 0;
  audioElement.current.src = countdownAudioSrc;

  if (reset) {
    setSeconds(initialSeconds);
  } else if (seconds === 0 && !reset) {
    audioElement.current.src = alarmAudioSrc;
    audioElement.current.play();
    setSeconds(initialSeconds);
    setTimeoutState(setTimeout(() => {
      audioElement.current.pause();
      audioElement.current.currentTime = 0;
    }, 2000));
  }

  if (start) {
    setTimeInterval(setInterval(secondUpdater, 999));
  } else {
    clearInterval(timeInterval);
  }
}, [start, onComplete, reset]);
 useEffect(() => {
    if (audioElement.current) {
      audioElement.current.volume = volume / 100;};
  }, [volume]);
  return (
    <><audio id="beep" ref={audioElement} 
        src={countdownAudioSrc} loop/>
      <div id='time-left'>{formatTime(seconds)}</div></>
  );
};
const IntervalClock = () =>{
 const [sessionLength, setSession] = useState(1500);
 const [breakLength, setBreak] = useState(300);
 const [breakMode, setBreakMode] = useState(false);
 const [start, setStart] = useState(false);
 const [reset, setReset] = useState(false);
  const handleSessionComplete = () => {
  setBreakMode(!breakMode)
  };
  
  const resetBtnHandler = () =>{
    setReset(!reset);
    setSession(1500);
    setBreak(300);
    setStart(false);
    setBreakMode(false);
  }
  const handleOnChange = (event)=>{
    (event.target.id==="session-range-input" || breakMode) && setReset(true);
    const newValue = parseInt(event.target.value, 10);
    event.target.id==="session-range-input" ? setSession(newValue * 60) : setBreak(newValue * 60)
  }
  
   return (
    <div className="container">
      <div className="time-container">
          <div id="timer-label">
            {breakMode ? "Break Time!" : "Session"}
          </div>
        <CountdownTimer
          initialSeconds={breakMode ? breakLength : sessionLength}
          onComplete={handleSessionComplete}
          start={start}
          reset={reset}
        />
      </div>
      <div className='box'>
      <div className='label-box'><label id="session-label" for="session-range-input">
        Session Length</label><div id='session-length'>{(sessionLength)/60}</div> <span>{formatTime(sessionLength)}</span>
        </div>
      <div className="input-box">
        <button
          className="btn"
          id="session-decrement"
          disabled={start}
          onClick={() => setSession(Math.max(sessionLength - 60, 60))}
        >
          -
        </button>
        <input
          id="session-range-input"
          type="range"
          min="1"
          max="60"
          value={sessionLength / 60}
          disabled={start}
          onChange={!start && handleOnChange}
        />
        <button
          className="btn"
          disabled={start}
          id="session-increment"
          onClick={() => setSession(Math.min(sessionLength + 60, 3600))}
        >
          +
        </button>
      </div>
      </div>
       <div className='box'>
     <div className='label-box'><label id="break-label" for="break-range-input">
        Break Length</label><div id='break-length'>{(breakLength)/60}</div>
       <span>{formatTime(breakLength)}</span>
       </div>
      <div className="input-box">
        <button
          className="btn"
          disabled={start}
          id="break-decrement"
          onClick={() => setBreak(Math.max(breakLength - 60, 60))}
        >
          -
        </button>
        <input
          id="break-range-input"
          type="range"
          min="1"
          max="60"
          value={breakLength / 60}
          disabled={start}
          onChange={!start && handleOnChange}
        />
        <button
          className="btn"
          disabled={start}
          id="break-increment"
          onClick={() => setBreak(Math.min(breakLength + 60, 3600))}
        >
          +
        </button>
      </div>
      </div>         
      <div className="button-container">
        <button
          class="btn"
          style={{ width: "100px" }}
          id="start_stop"
          onClick={() => {
            setStart(!start);
            setReset(false);
          }}
        >
          {start ? "Pause" : "Start"}
        </button>
        <button
          class="btn"
          style={{ width: "100px" }}
          id="reset"
          onClick={resetBtnHandler}
        >
          Reset
        </button>
      </div>
    </div>
  );
};

Did you know all you code HTML,CSS, & JS are all in one folder?

What do you mean? Yes I know the codePen structure but…

Well in the javascript file its very long and looks like you have all three there, I see you have all the files but was just wondering why?

Yes, I’ve decided to consolidate all the projects covered in the entire Frontend Developer certification under one place with a Windows 95 theme. It’s both more challenging and allows for a better outcome. While doing them separately with a simple design would have been easier, I haven’t encountered any issues so far. So… Is this a problem?

It very well could be because of the fact of the other two files html and css, do you still need those. If this is an spa single page app usually thats all done in html.

As a matter of fact, the HTML and CSS files are separate, I just send related component’s code in here als I didn’t use Tailwind or Sass. Additionally, for DOM manipulation, I’m using React, which is why HTML elements are in JavaScript. However, I’m still not sure what you mean :flushed::sweat_smile:. Here is the full version of code.

I just got 28 out of 29 to pass your were missing a </head> closing tag in the html file. Good luck

Thank you, I didn’t realize the element closing tag was missing :see_no_evil:
Also, yes I’ve pass 28 tests too, but unfortunately couldn’t pass the last one, so I asked for help…

I was wrong about the tag but it did show up as an error but now its not. Lol

1 Like

Hmm… I didn’t run your code, but looking at your reset function:

Maybe try deliberately stopping your audio element, and set its play position back to the start in the reset function.

myAudioElement.pause();
myAudioElement.currentTime = 0;

See if that helps with passing the tests…

Actually, what you said was correct. If I directly pause the audio element inside the resetBtnHandler, the problem could have been solved. It was just necessary to pass the audioElement ref outside. Previously, I had tried using context, but it created more overhead, so I couldn’t pass the test. Now, I realized that by using a ref callback, I can pass this ref from the child component to the parent component. And guess what? The problem was solved! :rofl:

const IntervalClock = () =>{
let audioElementRef = null; //Okay this is not so elegant but...
  const handleAudioRef = (ref) => {
    audioElementRef = ref;
  };
...
 const resetBtnHandler = () =>{
    if(audioElementRef){audioElementRef.pause();
    audioElementRef.currentTime=0;}
    setReset(!reset);
    setSession(1500);
    setBreak(300);
    setStart(false);
    setBreakMode(false);
  }
...
};

const CountdownTimer = ({ initialSeconds, onComplete, start, reset, audioRef }) => {
...
  useEffect(() => {
    audioRef(audioElement.current);
  }, [audioRef]);
...  

But it cause a new errors. Okay I think I can handle this :see_no_evil:

Edit: :partying_face::partying_face:

1 Like

At first I didn’t notice you had already paused the audio and set its .currentTime to zero. That’s why I had removed my thoughts; but only pausing, and resetting the audio inside of a timeout did not seem right to me. I’m glad your solving the problem! Best of luck to you!

1 Like