Search code examples
javascriptreactjskeydownkeyup

How to create countup timer with keyboard events in js


I am creating a program where the timer starts when i hit "keyup" and stops when i hit "keydown" and resets the timer when i hit "keydown" next time.

const Timer = () => {
  const [timer, setTimer] = useState(0);
  let state = 0;
  let isFired = false;
  const increment = useRef(null);

  useEffect(() => {
    window.addEventListener('keydown', (e) => {
      if (isFired) {
        if (e.code === 'Space' && state === 2) {
          e.stopPropagation();
          isFired = false;
          handleReset();
        }

        if (e.code === 'Space' && state === 1) {
          clearInterval(increment.current);
          state = 2;
        }
      }
    });

    window.addEventListener('keyup', (e) => {
      if (!isFired) {
        if (e.code === 'Space' && state === 0) {
          isFired = true;
          state = 1;
          handleStart();
        }
      }
    });

    return () => {
      window.removeEventListener('keydown', handleReset);
      window.removeEventListener('keyup', handleStart);
    };
  }, []);

  const handleStart = () => {
    increment.current = setInterval(() => {
    // DON'T COPY THIS BIT
      setTimer((timer) => timer + 10);
    }, 10);
  };

  const handleReset = () => {
    clearInterval(increment.current);
    setTimer(0);
    state = 0;
  };
  // DON'T COPY THIS BIT

  const formatTime = () => {
    console.log(timer);
    const getMilliSeconds = `0${timer % 60}`.slice(-2);
    const seconds = `${Math.floor(timer / 60)}`;
    const getSeconds = `0${seconds % 60}`.slice(-2);
    const getMinutes = `0${Math.floor(timer / 3600)}`.slice(-2);

    // return `${getHours} : ${getMinutes} : ${getSeconds}`;
    if (getMinutes === '00') {
      return `${getSeconds}.${getMilliSeconds}`;
    } else {
      return `${getMinutes} : ${getSeconds} : ${getMilliSeconds} `;
    }
  };

  // const formatTime = () => {
  //   // const milliSeconds = `0${}`;
  //   const seconds = `0${Math.floor(timer / 100)}`;
  //   const minute = `0${Math.floor(timer / 3600)}`.slice(-2);

  //   return `${minute}.${seconds}`;
  // };

  return (
    <div className="Timer">
      <h1>{formatTime()}</h1>
    </div>
  );
};

i have tried this so far it works most of the time but sometimes it gets glitchy and i know the time is not formated properly and also the increment is also wrong. Sometimes it stops on the "keydown" and fires the "keyup" in the same stroke making it reset the timer starting from zero I don't exactly know that if it fires keyup on the same stroke but it seems like it does

this is a link to what i have done so far timer


Solution

  • I checked your code and found it needs too many changes.

    1. You are using state and isFired as variables only but you need their persisted values on rerendering. Therefore, these must be states.
    2. You are not unbinding the same listener which you binded on keyup and keydown.
    3. As per your current code, your timer will start on 1st keyup and then next keydown it will stop as such where it was. Now, on next keydown it will reset to 0 and then on keyup, again timer will start from 0.

    I modified your code and see working changes here,

    https://codesandbox.io/s/xenodochial-shannon-im8zt?file=/src/App.js