Front End Development Libraries Projects - Build a 25 + 5 Clock

Tell us what’s happening:

Hi all. I’ve managed to get my project to pass all tests apart from #14 (and the audio ones, I’ve not yet implemented the audio as working on the timer logic).

As far as I can tell, the issue seems to be that just before this test my session length hits -36 and then it runs the timer with this session length. I’m not sure how this is possible because I can’t get it to hit -36 myself.

I’ve tried running with the old render method, this actually makes it pass far less tests than the current.

Your code so far

Do excuse this hot mess - it’s not been refactored yet!

//main.jsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'

ReactDOM.createRoot(document.getElementById('root')).render(
    <App />
)

//App.jsx
import { React, useState, useRef } from "react"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { 
  faArrowUp, 
  faArrowDown, 
  faPlay, 
  faPause, 
  faArrowsRotate } from "@fortawesome/free-solid-svg-icons";
import "./styles.css"

export default function App () {

  const [defaultTime, setDefaultTime] = useState({
    break: 5,
    session: 25,
  })

  const [timeRemaining, setTimeRemaining] = useState({
    sessionTime: defaultTime.session * 60,
    breakTime: defaultTime.break * 60
  })

  const stateRef = useRef();
  stateRef.current = timeRemaining;

  const [currentSession, setCurrentSession] = useState("Session")

  const sessionRef = useRef();
  sessionRef.current = currentSession

  const [intervalSetting, setIntervalSetting] = useState(null)

  let minutesRemainingSession = Math.floor(timeRemaining.sessionTime/60)
  let minutesRemainingDigitsSession = minutesRemainingSession > 9 ? minutesRemainingSession : "0" + minutesRemainingSession
  let secondsRemainingSession = timeRemaining.sessionTime % 60
  let secondsRemainingDigitsSession = secondsRemainingSession > 9 ? secondsRemainingSession : "0" + secondsRemainingSession

  let minutesRemainingBreak = Math.floor(timeRemaining.breakTime/60)
  let minutesRemainingDigitsBreak = minutesRemainingBreak > 9 ? minutesRemainingBreak : "0" + minutesRemainingBreak
  let secondsRemainingBreak = timeRemaining.breakTime % 60
  let secondsRemainingDigitsBreak = secondsRemainingBreak > 9 ? secondsRemainingBreak : "0" + secondsRemainingBreak

  function handleSettings (event) {
    const id = event.target.id

    if(defaultTime.break > 1){
      if(id == "break-decrement"){
        setDefaultTime(prevState => ({
            ...prevState,
            break: prevState.break - 1
        }));
        setTimeRemaining(prevState => ({
          ...prevState,
          breakTime: prevState.breakTime - 60
        }))
      }}
      if(defaultTime.session > 1){
        if(id == "session-decrement"){
          setDefaultTime(prevState => ({
            ...prevState,
            session: prevState.session - 1
          }))
          setTimeRemaining(prevState => ({
            ...prevState,
            sessionTime: prevState.sessionTime-60
          }))
      }
    }

    if(defaultTime.break < 60){
      if(id == "break-increment"){
        setDefaultTime(prevState => ({
            ...prevState,
            break: prevState.break + 1
        }));
        setTimeRemaining(prevState => ({
          ...prevState,
          breakTime: prevState.breakTime + 60
        }))
      }}
    if(defaultTime.session < 60){
      if(id == "session-increment"){
        setDefaultTime(prevState => ({
          ...prevState,
          session: prevState.session + 1
        }));
        setTimeRemaining(prevState => ({
          ...prevState,
          sessionTime: prevState.sessionTime + 60
        }))
      }
    }
  }

  function handleTimerStart () {

    if(!intervalSetting) {
      setIntervalSetting(setInterval(startTimer, 1000))
    }else {
      clearInterval(intervalSetting)
      setIntervalSetting(null)
    }

  }

  function startTimer () {

    if(stateRef.current.sessionTime > 0){
      setCurrentSession("Session")
      setTimeRemaining(prevState => ({
        ...prevState,
        sessionTime: prevState.sessionTime - 1
      }));

    }else { 
      setCurrentSession("Break");
    }

    if(sessionRef.current == "Break"){
      setTimeRemaining(prevState => ({
        ...prevState,
        breakTime: prevState.breakTime - 1
      }))
    }
    
    if (stateRef.current.breakTime == 0){
      setCurrentSession("Session");
      setTimeRemaining(prevState => ({
        ...prevState,
        sessionTime: defaultTime.session * 60,
        breakTime: defaultTime.break * 60
      }))
    }
  }


  function handleReset () {
      setCurrentSession("Session")
      clearInterval(intervalSetting);
      setIntervalSetting(null);

      setDefaultTime(prevState => ({
        ...prevState,
        break: 5,
        session: 25
      }));

      setTimeRemaining(prevState => ({
        sessionTime: 25 * 60,
        breakTime: 5 * 60
      }))
  }
  

  return (
    <div className="clock-wrapper">
      <h1>25 + 5 Clock</h1>

      <div className="clock-settings-wrapper">

          <div className="break-settings-wrapper">
            <h2 id="break-label">Break Length</h2>
            <div className="break-buttons-wrapper">
              <button onClick={handleSettings} id="break-decrement">
                <FontAwesomeIcon icon={faArrowDown} className="icons" />
              </button>
              <h3 id="break-length">{defaultTime.break}</h3>
              <button onClick={handleSettings} id="break-increment">
                <FontAwesomeIcon icon={faArrowUp} className="icons"/>
              </button>
            </div>
          </div>

          <div className="session-settings-wrapper">
            <h2 id="session-label">Session Length</h2>
            <div className="session-buttons-wrapper">
              <button onClick={handleSettings} id="session-decrement">
                <FontAwesomeIcon icon={faArrowDown} className="icons"/>
              </button>
              <h3 id="session-length">{defaultTime.session}</h3>
              <button onClick={handleSettings} id="session-increment">
                <FontAwesomeIcon icon={faArrowUp} className="icons"/>
              </button>
            </div>
          </div>

      </div>

      <div className="clock-body-wrapper">
        <h2 id="timer-label">{currentSession}</h2>
        <h3 id="time-left">
          {currentSession == "Session" ? 
          `${minutesRemainingDigitsSession}:${secondsRemainingDigitsSession}` : 
          `${minutesRemainingDigitsBreak}:${secondsRemainingDigitsBreak}`}
        </h3>
      </div>

      <div className="clock-buttons-wrapper">
        <div id="start_stop" onClick={handleTimerStart}>
          <FontAwesomeIcon icon={faPlay} />
          <FontAwesomeIcon icon={faPause} />
        </div>
        <div id="reset" onClick={handleReset}>
          <FontAwesomeIcon icon={faArrowsRotate} />
        </div>
      </div>
    </div>
  )
}

Your browser information:

User Agent is: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/119.0

Challenge Information:

Front End Development Libraries Projects - Build a 25 + 5 Clock

For ease I’ve hosted the app on my GitHub pages with the test script built in to the page.

GitHub link

I’m hopeful someone can ease me in the right direction, rather than flat out giving me the solution. I’ve worked on this for a number of evenings and managed to get the tests down to this final error (plus audio, but that should be a breeze).

1 Like

hello and welcome to fcc forum :slight_smile:

first of, very nice looking implementation, well done :clap:

“timer-label”

is not containing any “time” in it rather this text " Session"

hope this helps!! happy coding :slight_smile:

Hello,

Many thanks for your kind reply. You are too kind, I believe it’s a bit of a mess, but that’s what refactoring is for I suppose!

It turns out that the test setting the session length to -36 was a real issue. I couldn’t replicate it even with an autoclicker that clicked every millisecond for 50 clicks. I think there must be something deeply wrong in my methods to cause this issue.

The solution was really a hot fix and it comes nowhere near to fixing the bug:

  if(defaultTime.session < 1){
    setDefaultTime(prevState => ({
      ...prevState,
      session: 1
  }))
  }

I’ve just implemented the audio and I’m pleased to have passed 29/29 tests. This isn’t one that I want to refactor in a hurry but I’ve learnt a lot from and I know that refactoring this will be important for my learning… at some point! :wink:

Thanks for your help and kind welcome to this community.

Best,
Matt

1 Like

When using objects for the state, the useReducer hook is usually a better option than useState. You seem to have some unnecessary spreading of the previous state as well and if you do not need the previous state there is no reason to use the updater function.

I’m not convinced setting refs to state objects and relying on object reference changes is a good idea. At the very least I feel like it makes it harder to read and reason about.

Anyway, good job passing it.

Thanks for the feedback, much appreciated.

The only hooks I’ve come across so far as useState, useEffect and useRef. Though this was via the Scrimba React course because I struggled with the class components in FCC teaching.

The reason I came across useRef in the first place was because when I was using state in my if statements for some reason it wasn’t working correctly. The function updated state but then didn’t seem to work with the updated version of state on the next cycle. I can’t quite explain what was going on because I didn’t really understand, but a Google search introduced me to useRef and it worked so I rolled with it. Though I agree that it is hard to read and definitely needs refactoring.

Can you think why this might be the case? I believe it was in the startTimer function. With the if statements set to the timeRemaining state they didn’t seem to function correctly.

If you have any other hints on making the code more readable I’m all ears :slight_smile:

It’s hard to say without seeing the code but you may have been trying to use a state variable for a different state right after setting it. Just like how you can’t log the state after setting it.

You have refs containing state object references. When the state object changes so do the references. It is kind of like using indirect side effects, the state update is pure, but the object reference change is a side effect.

yeah, i saw that too but didnt mention it as that “test case” seem not much relevant to that, but im glad you figured it out, well done and happy coding :slight_smile: