4

Introduction

I am building the countdown timer, which I had built but now I want to scale it up. So I am building two countdowns on the same page with each of them having separate start, stop buttons, and common reset button.

What do I have?

I have implemented but they are not working independently. For example, when I click on the stop of the upper clock, it also stops the lower clock.

What do I want?

I wanted to have separate functions for both of them but on the same page.

Code

Most of the things are repeated in the code.

import React, { useState, useEffect, useRef } from "react";
import "./styles.css";

const STATUS = {
  STARTED: "Started",
  STOPPED: "Stopped"
};

const STATUSp1 = {
  STARTED: "Started",
  STOPPED: "Stopped"
};

export default function InputCountDown() {
  const [time, setTime] = useState(0);
  const [timep1, setTimep1] = useState(0);
  const [inc, setInc] = useState(0);

  const handleOnChange = (e) => {
    //console.log(e.target.value);
    setTime(e.target.value);
    setTimep1(e.target.value);
  };

  const handleOnChangeIncrement = (e) => {
    console.log(e.target.value);
    setInc(e.target.value);
  };

  const [secondsRemaining, setSecondsRemaining] = useState(time * 60);
  const [secondsRemainingp1, setSecondsRemainingp1] = useState(timep1 * 60);

  //console.log(time);

  const [status, setStatus] = useState(STATUS.STOPPED);
  const [statusp1, setStatusp1] = useState(STATUSp1.STOPPED);

  const secondsToDisplay = secondsRemaining % 60;
  const minutesRemaining = (secondsRemaining - secondsToDisplay) / 60;
  const minutesToDisplay = minutesRemaining % 60;
  const hoursToDisplay = (minutesRemaining - minutesToDisplay) / 60;

  const secondsToDisplayp1 = secondsRemainingp1 % 60;
  const minutesRemainingp1 = (secondsRemainingp1 - secondsToDisplayp1) / 60;
  const minutesToDisplayp1 = minutesRemainingp1 % 60;
  const hoursToDisplayp1 = (minutesRemainingp1 - minutesToDisplayp1) / 60;

  const handleStart = () => {
    setStatus(STATUS.STARTED);
    setSecondsRemaining(time * 60);
  };

  const handleStartp1 = () => {
    setStatusp1(STATUSp1.STARTED);
    setSecondsRemainingp1(timep1 * 60);
  };

  const handleStop = () => {
    setStatus(STATUS.STOPPED);
  };

  const handleStopp1 = () => {
    setStatusp1(STATUSp1.STOPPED);
  };

  const handleReset = () => {
    setStatus(STATUS.STOPPED);
    setSecondsRemaining(time * 60);
    setSecondsRemainingp1(timep1 * 60);
  };

  useInterval(
    () => {
      if (secondsRemaining > 0) {
        setSecondsRemaining(secondsRemaining - 1);
      } else {
        setStatus(STATUS.STOPPED);
      }

      if (secondsRemainingp1 > 0) {
        setSecondsRemainingp1(secondsRemainingp1 - 1);
      } else {
        setStatusp1(STATUSp1.STOPPED);
      }
    },
    status === STATUS.STARTED ? 1000 : null,
    statusp1 === STATUSp1.STARTED ? 1000 : null
    // passing null stops the interval
  );
  return (
    <>
      <div className="App">
        <h1>Countdown Using Input</h1>
        <div style={{ padding: "12px" }}>
          <label htmlFor="time"> Enter time in minutes </label>
          <input
            type="text"
            id="time"
            name="time"
            value={time}
            onChange={(e) => handleOnChange(e)}
          />
        </div>
        <div style={{ padding: "12px" }}>
          <label htmlFor="inc"> Enter increment </label>
          <input
            type="number"
            id="inc"
            name="inc"
            value={inc}
            onChange={(e) => handleOnChangeIncrement(e)}
          />
        </div>

        <button onClick={handleStart} type="button">
          Start
        </button>
        <button onClick={handleStop} type="button">
          Stop
        </button>
        <button onClick={handleReset} type="button">
          Reset
        </button>
        <div>
          <button
            onClick={() =>
              setSecondsRemaining(secondsRemaining + parseInt(inc, 10))
            }
          >
            Increment {inc} sec
          </button>
        </div>
        <div style={{ padding: 20, fontSize: "40px" }}>
          {twoDigits(hoursToDisplay)}:{twoDigits(minutesToDisplay)}:
          {twoDigits(secondsToDisplay)}
        </div>
        <div>Status: {status}</div>
        <div style={{ padding: 20, fontSize: "40px" }}>
          {twoDigits(hoursToDisplayp1)}:{twoDigits(minutesToDisplayp1)}:
          {twoDigits(secondsToDisplayp1)}
        </div>
        <button onClick={handleStartp1} type="button">
          Start_p1
        </button>
        <button onClick={handleStopp1} type="button">
          Stop_p1
        </button>
        <button onClick={handleReset} type="button">
          Reset_p1
        </button>
      </div>
    </>
  );
}

// source: https://overreacted.io/making-setinterval-declarative-with-react-hooks/
function useInterval(callback, delay) {
  const savedCallback = useRef();

  // Remember the latest callback.
  useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);

  // Set up the interval.
  useEffect(() => {
    function tick() {
      savedCallback.current();
    }
    if (delay !== null) {
      let id = setInterval(tick, delay);
      return () => clearInterval(id);
    }
  }, [delay]);
}

// https://stackoverflow.com/a/2998874/1673761
const twoDigits = (num) => String(num).padStart(2, "0");

You can also find the code in the codesandbox in the inputCountDown.js file .

Edit check-clock-phase1

image

You can ignore increment button

Please help me !!!

Sanket Shah
  • 2,888
  • 1
  • 11
  • 22
MagnusEffect
  • 3,363
  • 1
  • 16
  • 41
  • First of all, you are passing three arguments to `useInterval` but the function expects only two. That's a pretty silly mistake that should be caught by a linter. – marzelin Jan 04 '22 at 15:23

2 Answers2

3

You are using the same interval for two different counters, which causes them to sync up on the same time. Nevertheless, this code is handling too many responsibilities at once and contains unnecessary duplications that is polluting the readability of the code.

Instead of duplicating the same code with different variables names, try extracting it into its own component, and call that component twice. This way, more code isolation is ensured and is definitely less error prone, while improving readability.

Zaid Shawahin
  • 355
  • 2
  • 18
  • Ya but later there's interconnectivity between the two clocks , that's why keeping them in same component. – MagnusEffect Jan 04 '22 at 15:39
  • 1
    Even then, you can extract the interconnected part of the components and use it in the parent component, much bette than keeping two copies of the same code clogging up the parent for a functionality. – Zaid Shawahin Jan 04 '22 at 15:42
2

Seperate the useInterval logic for each of the timer

  useInterval(
    () => {
      if (secondsRemaining > 0) {
        setSecondsRemaining(secondsRemaining - 1);
      } else {
        setStatus(STATUS.STOPPED);
      }
    },
    status === STATUS.STARTED ? 1000 : null
  );

  useInterval(
    () => {
      if (secondsRemainingp1 > 0) {
        setSecondsRemainingp1(secondsRemainingp1 - 1);
      } else {
        setStatusp1(STATUSp1.STOPPED);
      }
    },
    statusp1 === STATUSp1.STARTED ? 1000 : null
    // passing null stops the interval
  );
Raphael
  • 61
  • 5