Thomas James
Thomas James

Reputation: 207

setTimout with pause/resume counter not updating on render

I would like to setup a counter which can be paused as well as resumed in React.js. But whatever I have tried so far is working the functionality part (pause/resume is working) but it's not updating the counter on render. Below is my code:

const ProgressBar = (props) => {
  const [isPlay, setisPlay] = useState(false);
  const [progressText, setProgressText] = useState(
    props.duration ? props.duration : 20
  );
  var elapsed,
    secondsLeft = 20;

  const timer = () => {
    // setInterval for every second
    var countdown = setInterval(() => {
      // if allowed time is used up, clear interval
      if (secondsLeft < 0) {
        clearInterval(countdown);
        return;
      }
      // if paused, record elapsed time and return
      if (isPlay === true) {
        elapsed = secondsLeft;
        return;
      }
      // decrement seconds left
      secondsLeft--;
      console.warn(secondsLeft);
    }, 1000);
  };
  timer();

  const stopProgress = () => {
    setisPlay(!isPlay);
    if (isPlay === false) {
      secondsLeft = elapsed;
    }
  };

  return (
    <>
      <p>{secondsLeft}</p>
    </>
  );
};

export default ProgressBar;

I have tried React.js state, global var type, global let type, react ref so far to make the variable global but none of them worked..

Upvotes: 0

Views: 1463

Answers (2)

ghkatende
ghkatende

Reputation: 487

import React, { useState, useEffect } from "react";

import logo from "./logo.svg";
import "./App.css";
var timer = null;
function App() {
  const [counter, setCounter] = useState(0);
  const [isplayin, setIsPlaying] = useState(false);

  const pause = () => {
    setIsPlaying(false);
    clearInterval(timer);
  };

  const reset = () => {
    setIsPlaying(false);
    setCounter(0);
    clearInterval(timer);
  };

  const play = () => {
    setIsPlaying(true);
    timer = setInterval(() => {
      setCounter((prev) => prev + 1);
    }, 1000);
  };
  return (
    <div className="App">
      <p>Counter</p>
      <h1>{counter}</h1>
      {isplayin ? (
        <>
          <button onClick={() => pause()}>Pause</button>
          <button onClick={() => reset()}>Reset</button>
        </>
      ) : (
        <>
          {counter > 0 ? (
            <>
              <button onClick={() => play()}>Resume</button>
              <button onClick={() => reset()}>Reset</button>
            </>
          ) : (
            <button onClick={() => play()}>Start</button>
          )}
        </>
      )}
    </div>
  );
}

export default App;

Upvotes: 1

Dmytro Krasnikov
Dmytro Krasnikov

Reputation: 1004

So basically why does your example not work? Your secondsLeft variable not connected to your JSX. So each time your component rerendered it creates a new secondsLeft variable with a value of 20 (Because rerendering is simply the execution of your function that returns JSX) How to make your variable values persist - useState or useReducer hook for react functional component or state for class based one. So react will store all the values for you for the next rerender cycle. Second issue is React doesn't rerender your component, it just doesn't know when it should. So what causes rerendering of your component -

  • Props change
  • State change
  • Context change
  • adding/removing your component from the DOM
  • Maybe I missing some other cases

So example below works fine for me

import { useEffect, useState } from "react";

function App() {
  const [pause, setPause] = useState(false);
  const [secondsLeft, setSecondsLeft] = useState(20);

  const timer = () => {
    var countdown = setInterval(() => {
      if (secondsLeft <= 0) {
        clearInterval(countdown);
        return;
      }

      if (pause === true) {
        clearInterval(countdown);
        return;
      }

      setSecondsLeft((sec) => sec - 1);
    }, 1000);
    return () => {
      clearInterval(countdown);
    };
  };
  useEffect(timer, [secondsLeft, pause]);

  const pauseTimer = () => {
    setPause((pause) => !pause);
  };

  return (
    <div>
      <span>Seconds Left</span>
      <p>{secondsLeft}</p>
      <button onClick={pauseTimer}>{pause ? "Start" : "Pause"}</button>
    </div>
  );
}

Upvotes: 1

Related Questions