ndresx / react-countdown

A customizable countdown component for React.
MIT License
746 stars 69 forks source link

setState in onTick callback resets the countdown. #231

Closed Zachaccino closed 1 year ago

Zachaccino commented 1 year ago

Dependencies

Node: 18.12.1 React Countdown: 2.3.5 Mantine UI: 5.1.0

Bug Description

I am creating a timer counting down towards 0. When using setState in the onTick callback, it automatically resets the timer everytime it is triggered. Additionally, timer functions correctly after removing the setState call.

Code

See the minimal working example here: https://codesandbox.io/s/autumn-dawn-372x98?file=/src/App.js

import "./styles.css";
import Countdown from "react-countdown";
import { useState } from "react";

export default function App() {
  const [countdownPercentage, setCountdownPercentage] = useState(100);

  return (
    <div className="App">
      <Countdown
        date={Date.now() + 100000}
        autoStart={true}
        controlled={false}
        onTick={(d) => {
          setCountdownPercentage(100 - (100 * (100000 - d.total)) / 100000);
          console.log(100 - (100 * (100000 - d.total)) / 100000);
        }}
      />
    </div>
  );
}

Note that using setState with a literal as shown below also don't reset the timer:

setCountdownPercentage(10);
Zachaccino commented 1 year ago

I found the solution. The setState triggers a re-render of the Countdown element, and because the date attribute is defined using Date.now() each re-render would reset the time.

By storing the timestamp at initialization, and use that as the offset, it works properly.

import "./styles.css";
import Countdown from "react-countdown";
import { useState } from "react";

export default function App() {
  const [countdownPercentage, setCountdownPercentage] = useState(100);
  const [initTimestamp, setInitTimestamp] = useState(Date.now());

  return (
    <div className="App">
      <Countdown
        date={initTimestamp + 100000}
        autoStart={true}
        controlled={false}
        onTick={(d) => {
          setCountdownPercentage(100 - (100 * (100000 - d.total)) / 100000);
          console.log(100 - (100 * (100000 - d.total)) / 100000);
        }}
      />
    </div>
  );
}

Love the fact that I got stuck for the entire day, decided to post an issue, and figured out immediately afterward.

ndresx commented 1 year ago

Hi @Zachaccino, glad you found a solution yourself! If I may suggest an alternative way of doing it, you could use React's useRef to also persist the date across re-renders (then you don't need to define the setter via useState).

Just something I wanted to bring up in case you were not aware of this yet!

Zachaccino commented 1 year ago

I was not aware of that! Thanks for letting me know, I'm still learning React!