Why is my interval calling my function even though its in a variable?

Viewed 44

I have a stopwatch that has buttons to start, stop, and reset the time. I assigned an interval to a variable to pause it using clearInterval every time I click the stop button, but my interval is calling the function even though I clicked no button. How do I fix this?

const startButton = document.getElementById('start');
const stopButton = document.getElementById('stop');
const resetButton = document.getElementById('reset');

const myInterval = setInterval(setTime, 10);
startButton.addEventListener('click', () => {
  setInterval(setTime, 10);
})
stopButton.addEventListener('click', ()=> {
  clearInterval(myInterval);
})

const timeUnits = ['00', '00', '00', '00'];
milliSeconds = 0;
seconds = 0;
minutes = 0;
hours = 0;

function setTime() {
  if (minutes == 60) {
    hours++;
    minutes = 0;
    timeUnits[0] = hours;
    timeUnits[1] = 0;
  } else if (seconds == 60) {
    minutes++;
    seconds = 0;
    timeUnits[1] = minutes;
    timeUnits[3] = 0;
  } else if (milliSeconds == 100) {
    seconds++;
    milliSeconds = 0;
    timeUnits[2] = seconds
    timeUnits[3] = 0;
    document.getElementById('para').innerHTML = timeUnits.join(':');
  } else {
    milliSeconds++;
    timeUnits[3] = milliSeconds;
    document.getElementById('para').innerHTML = timeUnits.join(':');
  };
}
<p id="para">00:00:00:00</p>
<button id="start">Start</button>
<button id="stop">Stop</button>
<button id="reset">Reset</button>

2 Answers

You set up the interval in two places:

const myInterval = setInterval(setTime, 10);     // <-- Here (A)
startButton.addEventListener('click', () => {
  setInterval(setTime, 10);                      // <-- and here (B)
})

In spot A (auto-start on page load) you save the interval ID into myInterval so that you can later do clearInterval(myInterval).

However, in spot B (on-demand start on "start" button click), you do not do that, so the interval set by clicking the "start" button can never be cleared.

This can be fixed by saving the interval into myInterval in both places:

let myInterval = setInterval(setTime, 10)
startButton.addEventListener('click', () => {
  myInterval = setInterval(setTime, 10)
})

Now, this created a bit of duplicate code, which is not ideal, and also there is still the problem that just clicking "start" multiple times would create multiple intervals running in parallel, overwriting the previous interval ID with the new one, making the old intervals again unclearable.

My suggestion for solving both of these issues is to create functions startInterval and stopInterval, where startInterval would also first call stopInterval before setting up a new interval, and stopInterval clearing the old one if it exists. Then you can call startInterval both on page load and on "start" button click, and stopInterval on "stop" button click:

let myInterval = null

function startInterval () {
  stopInterval()
  myInterval = setInterval(setTime, 10)
}

function stopInterval () {
  if (!myInterval) return
  clearInterval(myInterval)
  myInterval = null
}

startInterval()

startButton.addEventListener('click', startInterval)
stopButton.addEventListener('click', stopInterval)

From the wording of the question I'm not sure whether you really want to auto-start the timer though. In case you don't, you can simply remove the startInterval() line from my example (or in case of the original code, change the first assignment to let myInterval = null like in the second example).

This line:

const myInterval = setInterval(setTime, 10);

actually starts the interval.

You need to change it to this:

let myInterval;
startButton.addEventListener('click', () => {
  myInterval = setInterval(setTime, 10);
})

This ensures that everytime you press the start button the interval will be assigned to the myInterval variable again which can then be cleared by the stop button

Related