» Make Pomodoro Web App in React » 2. Development » 2.3 Timer

Timer

Enlighten the "start" button to animate the timer. Changes in App.js:

@@ -1,6 +1,54 @@
+import { useEffect, useRef, useState } from "react";
 import "./App.css";
+import { formatTime } from "./util";
+
+const POMODORO_SECONDS = 25 * 60;
 
 function App() {
+  const [seconds, setSeconds] = useState(POMODORO_SECONDS);
+  const [ticking, setTicking] = useState(false);
+
+  useEffect(() => {
+    if (seconds === 0) {
+      stopTimer();
+      alarm();
+    }
+  }, [seconds]);
+
+  // use the `useRef` hook to create a mutable object that persists across renders
+  const intervalIdRef = useRef(null);
+
+  const startTimer = () => {
+    setTicking(true);
+    intervalIdRef.current = setInterval(() => {
+      setSeconds((prevSeconds) => prevSeconds - 1);
+    }, 1000);
+  };
+
+  const stopTimer = () => {
+    setTicking(false);
+    if (intervalIdRef.current) {
+      clearInterval(intervalIdRef.current);
+    }
+  };
+
+  const resetTimer = () => {
+    stopTimer();
+    setSeconds(POMODORO_SECONDS);
+  };
+
+  const toggleTimer = () => {
+    if (ticking) {
+      stopTimer();
+    } else {
+      startTimer();
+    }
+  };
+
+  const alarm = () => {
+    // TODO: play some sound
+    console.log("Time's up!");
+  };
   return (
     <div className="app">
       <h1 className="app-name">
@@ -13,10 +61,15 @@ function App() {
         <span className="segment right-seg">Break</span>
       </div>
       <div className="card">
-        <h1 className="timer">25:00</h1>
-        <button className="control-btn">Start</button>
+        <h1 className="timer">{formatTime(seconds)}</h1>
+        <button className="control-btn" onClick={toggleTimer}>
+          {ticking ? "Pause" : "Start"}
+        </button>
       </div>
       <div className="settings">
+        <span className="setting-btn" onClick={resetTimer}>
+          Reset
+        </span>
         <span className="setting-btn">Settings</span>
       </div>
     </div>

The "tick-tock" is triggered by the setInterval() global function. This method returns an interval ID which uniquely identifies the interval, so you can remove it later by calling clearInterval().

The interval ID is shared between startTimer and stopTimer functions with the help of useRef.

Tune App.css a little bit:

 .settings {
   display: flex;
-  justify-content: flex-end;
+  justify-content: space-between;
 }

Add a new file named util.js:

export function formatTime(seconds) {
  const minutes = Math.floor(seconds / 60);
  const remainingSeconds = seconds % 60;

  const formattedMinutes = String(minutes).padStart(2, "0");
  const formattedSeconds = String(remainingSeconds).padStart(2, "0");

  return `${formattedMinutes}:${formattedSeconds}`;
}

Then you may click the "start" button and see it ticking.

Pomodoro Ticking