25 + 5 Clock - Why Does Test 8 in Content Fail?

Wow @jsdisco, you made this work with so little code and didn’t even bother with React or creating some fancy function that reads how much setInterval lagged and then adjusting for the next 1000ms run.

I think I’ll need to go back to the drawing board. The time that I’ve spent trying to wrangle state to account for time drift in setInterval is embarrassing.

How much experience do you have with JavaScript?

Thanks but errm the truth is I couldn’t make it work with React, so I went back to good old Vanilla. Still hurts a little.

About the time drift, I wasn’t even aware I’d have to take care of that. I think I mentioned above, with React it was easier not to use setInterval at all, but instead use setTimeout within useEffect hook, with the currentTime being inside the dependency array. setInterval and React don’t seem to go well together.

I’ve been writing JavaScript for about 18 months by the way. So the larger underlying concepts are still not something I have a good grasp at, but the fundamentals are solid by now.

@thompsonjl07 What did you do to care of the time delay?

Nothing that’s been successful.

I started over and rewrote it. Got the timer moving, but it’s one second behind the actual value of the number of seconds remaining and I don’t know how to fix that. Anyone get any ideas?

It also doesn’t stop when secondsRemaining reaches 0. It starts counting from negative numbers after reaching 0.

GitHub repository was updated as well so here’s the link again.

@thompsonjl07 Well, that sucks.

With a normal clock, calling the function used as the callback for setInterval would take care of the one second delay at the start. But here it just makes the clock move faster. Or maybe it was only because of the way I wrote the code for this project when I tried it last time.

It’s what I did here (a “normal” countdown timer app).

1 Like

Hey @DragonOsman, quite a thread you have here. I skimmed over it and your code and I’m curious why you decided against React for this project. One benefit of using react is that you have a single source of truth (state) when it comes to knowing what to display. I suppose you wouldn’t necessarily need react for that bit. For example, your increment and decrement functions are modifying the dom but they don’t store this info in any internal variables. Basing your numbers off of what’s in the dom can be unreliable. I think you’d benefit from having some internal state data that you derive all your display info from. Again you don’t necessarily need react for this. A single javascript object with all your commonly used data would suffice.

Something probably worth reading about (especially if you are committed to doing this without react) is the Model-view-controller design pattern. The extremely short tldr of it is that it separates the concerns of:

  • how should this be displayed on screen?
  • how does the logic work under the hood?

Right now a lot of your functions are dealing with both.

The other benefit of react is the virtual dom which basically does all the work for you in terms of deciding when and how to update what’s actually displayed. You give your components a single source of truth and if any of that changes the dom will update. I don’t wanna sound too much like I’m evangelizing for react but it really does make this project easier.

I wanted to try it with both jQuery and React. And the way I’ve been doing the projects in the Front End Libraries curriculum is that I did one in React and one in jQuery. I did the calculator project in React so I thought I’d try this one in jQuery. Nothing else to it.

By the way. How do I play an audio element in jQuery? I tried doing $("#beep").play() but my browser console gave me an error saying, "’$(…).play’ is not a function’. What do I do about this?

jquery and react don’t really accomplish the same thing. Jquery makes dom manipulation very simple but doesn’t care about how you accomplish your logic. React is much more opinionated.

I found this: html5 audio player - jquery toggle click play/pause? - Stack Overflow

@rankinaaron I’m not doing this because I think jQuery and React are similar or that they let you do the same thing. It’s just because the specs for each project say you can use any library or framework out of the ones taught in the section and because I want to show on my portfolio page that I can use jQuery, React and SASS (I’m using SASS instead of plain CSS).

Thanks for the link.

The timer won’t stop when secondsRemaining and the clock have reached zero. How do I get it stop? Here’s the current code:

"use strict";

let isSessionRunning = true;
let isClockRunning = false;

const addLeadingZeroes = time => {
  return time < 10 ? `0${time}` : time;
};

const timerElem = $("#time-left");
let timerTextContent = timerElem.text();
const defaultSessionLength = "25";
const defaultBreakLength = "5";
let secondsRemaining = (isSessionRunning ?
  parseInt(defaultSessionLength) :
  parseInt(defaultBreakLength)) * 60;

if (isSessionRunning && $("#session-length").text() !== defaultSessionLength) {
  secondsRemaining = Number($("#session-length").text()) * 60;
} else if (!isSessionRunning && $("#break-length").text() !== defaultBreakLength) {
  secondsRemaining = Number($("#break-length").text()) * 60;
}

if (isSessionRunning && $("#break-length").text() !== defaultBreakLength) {
  secondsRemaining = Number($("#session-length").text()) * 60;
}

let minutes = parseInt(secondsRemaining / 60);
let seconds = parseInt(secondsRemaining % 60);
timerTextContent = `${addLeadingZeroes(minutes)}:${addLeadingZeroes(seconds)}`;
timerElem.text(timerTextContent);

let timerId;
const initializeClock = () => {
  if (!isClockRunning) {
    if (secondsRemaining <= 0 && $("#time-left").text() === "00:00") {
      clearInterval(timerId);
      $("#beep").click();
      $("#beep").on("click", () => this.play());
      timerTextContent = "00:00";
      timerElem.text(timerTextContent);
      if (isSessionRunning) {
        isSessionRunning = false;
        $("#timer-label").text("Break");
        secondsRemaining = Number($("#break-length").text()) * 60;
      } else {
        isSessionRunning = true;
        $("#timer-label").text("Session");
        secondsRemaining = Number($("session-length").text()) * 60;
      }
    }

    timerId = setInterval(() => {
      isClockRunning = true;
      $("#session-increment").prop("disabled", true);
      $("#session-decrement").prop("disabled", true);
      $("#break-increment").prop("disabled", true);
      $("#break-decrement").prop("disabled", true);

      minutes = parseInt(secondsRemaining / 60);
      seconds = parseInt(secondsRemaining % 60);
      timerTextContent = `${addLeadingZeroes(minutes)}:${addLeadingZeroes(seconds)}`;
      timerElem.text(timerTextContent);
      secondsRemaining--;
      console.log(secondsRemaining);
    }, 1000);
  } else {
    clearInterval(timerId);
    isClockRunning = false;
    $("#session-increment").prop("disabled", false);
    $("#session-decrement").prop("disabled", false);
    $("#break-increment").prop("disabled", false);
    $("#break-decrement").prop("disabled", false);
  }
};

const startStopButton = $("#start_stop");
startStopButton.on("click", initializeClock);

const breakIncrementButton = $("#break-increment");
breakIncrementButton.on("click", () => {
  const lengthValueElem = $("#break-length");
  let lengthValue = Number(lengthValueElem.text());
  if (lengthValue < 60) {
    lengthValue++;
    lengthValueElem.text(lengthValue.toString());
    const minutes = (isSessionRunning) ?
      Number($("#session-length").text()) :
      Number($("#break-length").text());
    timerTextContent = `${addLeadingZeroes(minutes)}:00`;
    timerElem.text(timerTextContent);
  } else if (lengthValue === 60) {
    lengthValue;
  }
});

const breakDecrementButton = $("#break-decrement");
breakDecrementButton.on("click", () => {
  const lengthValueElem = $("#break-length");
  let lengthValue = Number(lengthValueElem.text());
  if (lengthValue > 1) {
    lengthValue--;
    lengthValueElem.text(lengthValue.toString());
    const minutes = (isSessionRunning) ?
      Number($("#session-length").text()) :
      Number($("#break-length").text());
    timerTextContent = `${addLeadingZeroes(minutes)}:00`;
    timerElem.text(timerTextContent);
  } else if (lengthValue === 1) {
    lengthValue;
  }
});

const sessionIncrementButton = $("#session-increment");
sessionIncrementButton.on("click", () => {
  const lengthValueElem = $("#session-length");
  let lengthValue = lengthValueElem.text();
  if (lengthValue < 60) {
    lengthValue++;
    lengthValueElem.text(lengthValue.toString());
    const minutes = (isSessionRunning) ?
      Number($("#session-length").text()) :
      Number($("#break-length").text());
    timerTextContent = `${addLeadingZeroes(minutes)}:00`;
    timerElem.text(timerTextContent);
  } else if (lengthValue === 60) {
    lengthValue;
  }
});

const sessionDecrementButton = $("#session-decrement");
sessionDecrementButton.on("click", () => {
  const lengthValueElem = $("#session-length");
  let lengthValue = lengthValueElem.text();
  if (lengthValue > 1) {
    lengthValue--;
    lengthValueElem.text(lengthValue.toString());
    const minutes = (isSessionRunning) ?
      Number($("#session-length").text()) :
      Number($("#break-length").text());
    timerTextContent = `${addLeadingZeroes(minutes)}:00`;
    timerElem.text(timerTextContent);
  } else if (lengthValue === 1) {
    lengthValue;
  }
});

A hint would be appreciated.

Do you want it to stop when it reaches zero? Shouldn’t it rollover to break and continue running?

Off the top of my head I would think you need something in your interval that checks if time remaining has reached zero so you know to switch the session type. Since the interval runs the callback every second that should keep track of whether you’ve hit 0.

The way you are checking now, you are only checking if the clock has hit 0 immediately after you start the timer, in which case it will never be 0.

I already do have code to make the switch I believe (unless it’s somehow wrong?).

I’m trying calling it inside the callback function now. It’s running currently. I’ll see what happens.

@rankinaaron thanks for chiming in on this project too. It’s been more difficult than I anticipated.

My first instinct was to use React for this, but I was getting quite frustrated with overcoming the challenge of time drift. There are many threads out there about how setInterval drifts over time and ways to get around it. Between implementing a solution for that and weaving all the React hook rules together, it just wasn’t working for this newbie.

Then… @jsdisco posted a vanilla solution and it blew my mind. I don’t remember anything about his code except that it was vanilla and a lot shorter than all my React code.

So today, I just started from scratch and within about 3 hours I finished everything by going vanilla.

React version: https://codesandbox.io/s/fcc-fel5-dual-interval-timer-1018-2110-stable-but-with-reset-error-87cle

Vanilla version: https://codesandbox.io/s/dual-interval-timer-b378v

Vanilla passes all tests. Unfortunately in both versions I keep getting one of these “The error you provided does not contain a stack trace.” I also understand that the point of this project was to use some kind of front end library. So there is a little cleaning up to do yet. Maybe I’ll implement jQuery just to say that I did.

I found it more difficult than expected too. I only finished this a few weeks ago. Here is mine: https://codepen.io/rankinaaron/pen/PoNKrxY

The thing is, setInterval time drift has nothing to do with React and as far as I can tell your vanilla code does nothing to mitigate it. I don’t think it’s a huge issue though for this project, to be honest. The way I implemented it was to use the Date object to get an accurate time elapsed since starting the clock. This has the potential to skip numbers in the display though. Dealing in milliseconds also caused me more trouble than it was worth and I ended up just doing some inaccurate rounding anyway. The example project uses a library called accurateInterval which solves the problem quite nicely and can be used just like setInterval.

just trying to save @DragonOsman the headache as I think basing your calculations off of whats displayed in the dom will come back to bite you in all sorts of weird ways. But then he probably knows his own code better than I do.

1 Like

I got the timer to make the switch after reaching zero, but there’s still an issue. And it also doesn’t pass all of the tests for the increment and decrement buttons (just the one (only passing the one for not letting the session or break length become less than or equal to zero).

I can see that my increment and decrement button implementation doesn’t allow the lengths to reach 0 or to go above 60. So what’s the problem?

Here’s the GitHub repo link again. Updated.

@rankinaaron How do you mean how should do it if I shouldn’t base my calculations off of what’s displayed in the DOM? Is this possible in jQuery or plain JavaScript?

Anyway, read and try out my updated code and see what’s happening. Then help me see what I’m doing wrong that could be causing the weird issue after making the switch from session to break.

@DragonOsman, do you have this in a sandbox anywhere? It’s much easier to check out what’s going on when you post to fiddle, for example.

My audio file and the image I have for the reset button won’t be in there, will they? I need a way to make them work in the sandbox.

What I mean is that you have lines like this:

secondsRemaining = Number($("#break-length").text()) * 60;

It might not be immediately obvious but the number displayed in the dom (the number you are grabbing with this part Number($("#break-length").text()) is generally less reliable than a variable in your javascript code. The alternative would be to have your break length stored in a variable somewhere, change it accordingly, and use that value to update the dom. When I say dom I’m talking about what’s actually rendered to the page. And yeah I’m not saying it’s wrong but that it can cause you trouble in ways you don’t expect down the line.

Do you have your project in codepen? It would help if I could see which tests are failing and the logs. Even a screenshot would work.

For my audio file, I’ve just been using an https source reference. The sound plays… but I do seem to have an audio related error that I’ll need to figure out. You could host your image somewhere too and reference it like anything else.

Here’s the pen: https://codepen.io/DragonOsman/pen/RwRozjY . this.play() gives me an error “this.play is not a function”, though.

I uploaded the audio and image files to filebin and brought in the URLs, but it didn’t work. I probably did it wrong.