1

I'm working on React pomodoro clock and I got stuck on joining if/else with setTimeout(). Here is my code:

React.useEffect(() => {
    if(props.isOn === true) {
      if(props.timeLeft <= 0) {
        setTimeout(function() {
          if(props.activeClockType === 'Session') {
            props.handleClockType('Break');
            props.handleTimeLeft(props.breakLength * 60);
          } else {
            props.handleClockType('Session');
            props.handleTimeLeft(props.sessionLength * 60);
          }
        }, 1000);
      }
      const id = setInterval(count, 1000);
      return () => clearInterval(id);
    }
  });

When the clock goes to 00:00 it's stopping for 1sec and then showing Break and timer sets to breakLength. Great, but after 1sec it's not counting down but changing back to Session and counting sessionLength.

When I delete setTimeout (commented in CodePen) it works good but it's not stopping at 00:00 for 1sec but immediately changes on Break what looks ugly.

How to make it stop on 00:00 for 1 second before counting other activeClockType?

I've spent whole evening on looking for any clue but still I don't have any idea what is wrong. I tried to put setTimeout in every possible combination with if but effect is always the same. From this topic I suspect that I should probably use clearTimeout but also tried it in any combination and got nothing.

Or maybe I should try another possibility to run countdown than useEffect()? Earlier I made working timer in class component (commented in CodePen) with componentWillReceiveProps but it's deprecated and my attempts to change it on other functions gave nothing. I don't fully understand this Effect Hook but it was the only way I found that is counting well. And it's shorter than class component.

Here is my full code on CodePen

Thank you in advance.

Milo K
  • 65
  • 1
  • 8

1 Answers1

2

I think there is two problem here:

1) Every time you use the hook to set the state, you fired the React.useEffect, if you set the state twice, the React.useEffect would be fired twice.

2) use setTimeout to replace setInterval to avoid update state too many times.

Problems are showing below:

  React.useEffect(() => {
    if(props.isOn === true) {
      if(props.timeLeft <= 0 && !changeClockType) {
        setTimeout(function() {
          if(props.activeClockType === 'Session') {
            props.handleClockType('Break');   // ==> Fired update and then excuted React.useEffect
            props.handleTimeLeft(props.breakLength * 60); //  ==> Fired update again and then excuted React.useEffect again
          } else {
            props.handleClockType('Session');
            props.handleTimeLeft(props.sessionLength * 60);
          }
        }, 1000);
      }
      const id = setTimeout(count, 1000);  // ==> modify setInterval to setTimeout 
      return () => clearInterval(id);
    }

  });

I try to modify the code like this:

let changeClockType = false;  // ==> Should be put here because it couldn't be put in the function component
function Timer(props) {

  const count = () => props.handleTimeLeft(props.timeLeft - 1);

  function startStop() {
    props.handleIsOn(props.isOn === false ? true : false);
  }

  React.useEffect(() => {
    console.log("step 1");
    if(props.isOn === true) {   console.log("step 2");
      if(props.timeLeft <= 0 && !changeClockType) {   console.log("step 3");
        setTimeout(function() {
          if(props.activeClockType === 'Session') {   console.log("step 4");
            changeClockType = true;
            props.handleClockType('Break');   // ==> Fired update and then excuted React.useEffect
            console.log("Change to Break")
            props.handleTimeLeft(props.breakLength * 60); //  ==> Fired update again and then excuted React.useEffect again
            changeClockType = false;
            console.log("Set breakLength")
          } else {   console.log("step 5");
            changeClockType = true;
            props.handleClockType('Session');
            props.handleTimeLeft(props.sessionLength * 60);
            changeClockType = false;
          }
        }, 1000);
      }
      const id = setTimeout(count, 1000);  // ==> modify setInterval to setTimeout 
      return () => clearInterval(id);
    }
  });

The code isn't so clean but can be run well.

tinwan
  • 307
  • 1
  • 4
  • Works perfectly! Thank you very much. – Milo K Mar 10 '19 at 20:07
  • BTW why this let is declared outside the function? I tried and it's actually not working if declared inside. It works if declared as useState hook. What is better normal declaration as you did or hook? – Milo K Mar 10 '19 at 20:13
  • 1
    Because if changeClockType is putted in Timer function, when the **props.handleClockType('Break')** is executing, the function **Timer** will execute and the changeClockType's value will be initialed. And after the Timer function executed , then **props.handleTimeLeft(props.breakLength * 60)** will execute. You can try the code and looking the log in console. – tinwan Mar 11 '19 at 03:18