Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementing a countdown timer in React with Hooks

Tags:

Im trying to render a count down timer on screen with react hooks, but I'm not sure the best way to render it.

I know i'm supposed to use the useEffect to compare current state to previous state, but I don't think I'm doing it correctly.

I would appreciate the help!

I've tried a couple of different ways, none of them work, like setting state whenever whenever it updates, but it just ends up flickering like crazy.



const Timer = ({ seconds }) => {
    const [timeLeft, setTimeLeft] = useState('');

    const now = Date.now();
    const then = now + seconds * 1000;

    const countDown = setInterval(() => {
        const secondsLeft = Math.round((then - Date.now()) / 1000);
        if(secondsLeft <= 0) {
            clearInterval(countDown);
            console.log('done!');
            return;
        }
        displayTimeLeft(secondsLeft);
    }, 1000);

    const displayTimeLeft = seconds => {
        let minutesLeft = Math.floor(seconds/60) ;
        let secondsLeft = seconds % 60;
        minutesLeft = minutesLeft.toString().length === 1 ? "0" + minutesLeft : minutesLeft;
        secondsLeft = secondsLeft.toString().length === 1 ? "0" + secondsLeft : secondsLeft;
        return `${minutesLeft}:${secondsLeft}`;
    }

    useEffect(() => {
        setInterval(() => {
            setTimeLeft(displayTimeLeft(seconds));
        }, 1000);
    }, [seconds])


    return (
        <div><h1>{timeLeft}</h1></div>
    )
}

export default Timer;```
like image 791
niinja no Avatar asked Jul 21 '19 21:07

niinja no


People also ask

How do you make a countdown timer with React Hooks?

import { useEffect, useState } from 'react'; const useCountdown = (targetDate) => { const countDownDate = new Date(targetDate). getTime(); const [countDown, setCountDown] = useState( countDownDate - new Date(). getTime() ); useEffect(() => { const interval = setInterval(() => { setCountDown(countDownDate - new Date().

What is componentDidUpdate in React Hooks?

What is componentDidUpdate in Hooks? Each React part has a lifecycle, and one of them is componentDidUpdate. This lifecycle is when the React component state or prop value is updated. In class components, use the componentDidUpdate method to trigger side effects for the life cycle.


2 Answers

const Timer = ({ seconds }) => {
  // initialize timeLeft with the seconds prop
  const [timeLeft, setTimeLeft] = useState(seconds);

  useEffect(() => {
    // exit early when we reach 0
    if (!timeLeft) return;

    // save intervalId to clear the interval when the
    // component re-renders
    const intervalId = setInterval(() => {
      setTimeLeft(timeLeft - 1);
    }, 1000);

    // clear interval on re-render to avoid memory leaks
    return () => clearInterval(intervalId);
    // add timeLeft as a dependency to re-rerun the effect
    // when we update it
  }, [timeLeft]);

  return (
    <div>
      <h1>{timeLeft}</h1>
    </div>
  );
};
like image 153
Asaf Aviv Avatar answered Sep 20 '22 06:09

Asaf Aviv


You should use setInterval. I just wanted to add a slight improvement over @Asaf solution. You do not have to reset the interval every time you change the value. It's gonna remove the interval and add a new one every time (Might as well use a setTimeout in that case). So you can remove the dependencies of your useEffect (i.e. []):

function Countdown({ seconds }) {
  const [timeLeft, setTimeLeft] = useState(seconds);

  useEffect(() => {
    const intervalId = setInterval(() => {
      setTimeLeft((t) => t - 1);
    }, 1000);
    return () => clearInterval(intervalId);
  }, []);

  return <div>{timeLeft}s</div>;
}

Working example:

Countdown example

Note in the setter, we need to use this syntax (t) => t - 1 so that we get the latest value each time (see: https://reactjs.org/docs/hooks-reference.html#functional-updates).


Edit (22/10/2021)

If you want to use a setInterval and stop the counter at 0, here is what you can do:

function Countdown({ seconds }) {
  const [timeLeft, setTimeLeft] = useState(seconds);
  const intervalRef = useRef(); // Add a ref to store the interval id

  useEffect(() => {
    intervalRef.current = setInterval(() => {
      setTimeLeft((t) => t - 1);
    }, 1000);
    return () => clearInterval(intervalRef.current);
  }, []);

  // Add a listener to `timeLeft`
  useEffect(() => {
    if (timeLeft <= 0) {
      clearInterval(intervalRef.current);
    }
  }, [timeLeft]);

  return <div>{timeLeft}s</div>;
}

Countdown example

like image 36
Elfayer Avatar answered Sep 19 '22 06:09

Elfayer