skip to Main Content

const [lap, setLap] = useState([]);

function start(){
       if(!isStart){
            starting = Date.now() - elapsed;
            isStart = true;
            timer = setInterval(update, 1000);  
            console.log("timer is activated");
    }
}
 function stop(){
        if(isStart){
            isStart = false;
            clearInterval(timer);
    }
}
function reset(){
        isStart = false;
        elapsed = 0;
        document.getElementById("time").textContent = "00:00:00";
        clearInterval(timer);
        starting = 0;
    }
}
function update(){
        var curr = Date.now();
        elapsed = curr - starting;
        var min = Math.floor(elapsed/(1000*60));
        var sec = Math.floor((elapsed/1000)%60);
        var msec = Math.floor((elapsed%1000)/10);
        var time = [min, sec, msec]
        time = time.map((element) => pad(element));
        var time_str = time[0] + ":" + time[1] + ":" + time[2];
        document.getElementById("time").textContent = time_str;
        console.log(time_str)
    }
const lap_update = (val) => {
        setLap(lap => [...lap, val]);
    }
<div id="lap" onClick={() => lap_update(document.getElementById("time").textContent)}>LAP</div>

The above are the code snippets for a Stopwatch is lap functionality.

Unfortunately, my lap_update function when clicked, is causing the start/stop/reset functions to malfunction.

(Stop function and reset function not working, and start function is double rendering).

I really am not able to understand why, and what changes are needed in the setLap statment.

Please help!

Trying to figure out the issue with the setLap statement which is causing the other functions to malfunction (till Lap function is called, the program is running fine).

2

Answers


  1. when you update the lap state with setLap it causes a rerender of the component. So, If the start function is coupled to a re-render in some way it might get invoked many times

    for this, we can use a useRef hook to keep track of the timer and check if the stopwatch is running to avoid issues with rerenders. this will make sure updates to the state don’t interfere with the timing functions.

    I have made these changes to youre code and here is a working example

    export const Stopwatch = () => {
      const [lap, setLap] = useState([]);
      const [elapsed, setElapsed] = useState(0);
      const [isStart, setIsStart] = useState(false);
      const timerRef = useRef(null);
      const startingRef = useRef(0);
    
      const pad = (num) => (num < 10 ? '0' + num : num);
    
      const start = () => {
        if (!isStart) {
          startingRef.current = Date.now() - elapsed;
          setIsStart(true);
          timerRef.current = setInterval(update, 1000);
          console.log("timer is activated");
        }
      };
    
      const stop = () => {
        if (isStart) {
          setIsStart(false);
          clearInterval(timerRef.current);
        }
      };
    
      const reset = () => {
        setIsStart(false);
        setElapsed(0);
        document.getElementById("time").textContent = "00:00:00";
        clearInterval(timerRef.current);
        startingRef.current = 0;
      };
    
      const update = () => {
        const curr = Date.now();
        const elapsed = curr - startingRef.current;
        setElapsed(elapsed);
        const min = Math.floor(elapsed / (1000 * 60));
        const sec = Math.floor((elapsed / 1000) % 60);
        const msec = Math.floor((elapsed % 1000) / 10);
        const time = [min, sec, msec].map(pad);
        const time_str = `${time[0]}:${time[1]}:${time[2]}`;
        document.getElementById("time").textContent = time_str;
        console.log(time_str);
      };
    
      const lap_update = () => {
        setLap((lap) => [...lap, document.getElementById("time").textContent]);
      };
    
      return (
        <div>
          <div id="time">00:00:00</div>
          <div id="lap" onClick={lap_update}>LAP</div>
          <button onClick={start}>Start</button>
          <button onClick={stop}>Stop</button>
          <button onClick={reset}>Reset</button>
          <ul>
            {lap.map((lapTime, index) => (
              <li key={index}>{lapTime}</li>
            ))}
          </ul>
        </div>
      );
    };
    

    the timerRef keeps track of the interval timer. this will prevent issues caused by rerenders
    startingRef is for holding the starting time to have a consistent timing calculations even when the component rerenders

    Read more about useRef hook here

    Login or Signup to reply.
  2. The issue with setLap function causing other functions to malfunction in your React application likely stems from how state and side effects are being managed in React, especially when dealing with functional components and hooks.

    Let’s encapsulate your stopwatch logic within React component and make sure that state changes are managed correctly.

    
    import React, { useState, useRef, useEffect } from 'react';
    
    const Stopwatch = () => {
      const [isRunning, setIsRunning] = useState(false);
      const [elapsed, setElapsed] = useState(0);
      const [laps, setLaps] = useState([]);
      const timerRef = useRef(null);
      const startTimeRef = useRef(0);
    
      const start = () => {
        if (!isRunning) {
          startTimeRef.current = Date.now() - elapsed;
          setIsRunning(true);
          timerRef.current = setInterval(update, 1000);
          console.log('timer is activated');
        }
      };
    
      const stop = () => {
        if (isRunning) {
          setIsRunning(false);
          clearInterval(timerRef.current);
        }
      };
    
      const reset = () => {
        setIsRunning(false);
        setElapsed(0);
        clearInterval(timerRef.current);
        startTimeRef.current = 0;
        document.getElementById('time').textContent = '00:00:00';
      };
    
      const update = () => {
        const curr = Date.now();
        setElapsed(curr - startTimeRef.current);
      };
    
      useEffect(() => {
        if (!isRunning) return;
        const interval = setInterval(() => {
          const curr = Date.now();
          setElapsed(curr - startTimeRef.current);
        }, 1000);
    
        return () => clearInterval(interval);
      }, [isRunning]);
    
      useEffect(() => {
        const min = Math.floor(elapsed / (1000 * 60));
        const sec = Math.floor((elapsed / 1000) % 60);
        const msec = Math.floor((elapsed % 1000) / 10);
        const time = [min, sec, msec].map(pad).join(':');
        document.getElementById('time').textContent = time;
        console.log(time);
      }, [elapsed]);
    
      const pad = (num) => (num < 10 ? '0' + num : num);
    
      const lapUpdate = () => {
        setLaps((laps) => [...laps, document.getElementById('time').textContent]);
      };
    
      return (
        <div>
          <div id="time">00:00:00</div>
          <button onClick={start}>Start</button>
          <button onClick={stop}>Stop</button>
          <button onClick={reset}>Reset</button>
          <div id="lap" onClick={lapUpdate}>
            LAP
          </div>
          <ul>
            {laps.map((lap, index) => (
              <li key={index}>{lap}</li>
            ))}
          </ul>
        </div>
      );
    };
    
    export default Stopwatch;
    

    Why Your Original Code Malfunctions:

    Direct DOM Manipulation: Directly manipulating the DOM (document.getElementById('time').textContent) can conflict with React’s virtual DOM updates.

    State Not Synchronized: Your functions were relying on local variables (isStart, elapsed) that were not synchronized with React’s state management, leading to inconsistent behavior.

    State Changes and Re-renders: Calling setLap inside an event handler triggers a state update, causing a re-render. If the component isn’t structured to handle re-renders properly, this can lead to unexpected behavior.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search