ndresx / react-countdown

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

You guys that feel like this is trash, all those closed issues aren't even actually fixed. I was so determined to use this trash that I spent my night out on it. #259

Closed Chris-Imade closed 2 weeks ago

Chris-Imade commented 1 month ago

If you have any issues with it. Just scrap it and use your own countdown timer.

` import React, { useEffect, useState } from 'react';

interface CountdownProps { dateTime: Date; onComplete?: () => void; }

const calculateTimeLeft = (targetDate: Date): { hours: number; minutes: number; seconds: number } => { const now = new Date(); const difference = targetDate.getTime() - now.getTime();

if (difference <= 0) { return { hours: 0, minutes: 0, seconds: 0 }; }

const hours = Math.floor((difference % (1000 60 60 24)) / (1000 60 60)); const minutes = Math.floor((difference % (1000 60 60)) / (1000 60)); const seconds = Math.floor((difference % (1000 * 60)) / 1000);

// Handle NaN scenario return { hours: isNaN(hours) ? 0 : hours, minutes: isNaN(minutes) ? 0 : minutes, seconds: isNaN(seconds) ? 0 : seconds, }; };

const Countdown: React.FC = ({ dateTime, onComplete }) => { const [timeLeft, setTimeLeft] = useState(calculateTimeLeft(dateTime));

useEffect(() => { const interval = setInterval(() => { const newTimeLeft = calculateTimeLeft(dateTime); setTimeLeft(newTimeLeft);

  if (newTimeLeft.hours === 0 && newTimeLeft.minutes === 0 && newTimeLeft.seconds === 0) {
    clearInterval(interval);
    if (onComplete) onComplete();
  }
}, 1000);

return () => clearInterval(interval);

}, [dateTime, onComplete]);

return (

{String(timeLeft.hours).padStart(2, '0')}: {String(timeLeft.minutes).padStart(2, '0')}: {String(timeLeft.seconds).padStart(2, '0')}

); };

export default Countdown; `

You should pass a Date object and a callback method

ndresx commented 1 month ago

Hello, thanks for your input! Could you elaborate on what caused you to spend the night on it?

Chris-Imade commented 1 month ago

It doesn't re-render when the prop does. This is brief I know, but I am working on this app where I need to display 15mins countdown on each 15mins interval of time, so when the countdown reaches 00:00:00 I pass the next 15mins interval to it so that it shows another 15mins countdown. In my case it never re-rendered. This made me try different ways to force it's re-render but to no avail.

There was also a scenario where before it updated there'd be a 47sec or there about delay.

Suggested fix:

I'll share code to how my issue was resolved...:

` interface CountdownProps { dateTime: Date; }

const calculateTimeLeft = ( targetDate: Date, ): { hours: number; minutes: number; seconds: number } => { const now = new Date(); const difference = targetDate.getTime() - now.getTime();

if (difference <= 0) {
    return { hours: 0, minutes: 0, seconds: 0 };
}

const hours = Math.floor(
    (difference % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60),
);
const minutes = Math.floor((difference % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((difference % (1000 * 60)) / 1000);

return { hours, minutes, seconds };

};

const Countdown: React.FC = ({ dateTime }) => { const [timeLeft, setTimeLeft] = useState(calculateTimeLeft(dateTime)); const [hasRunOnce, setHasRunOnce] = useState(false); // Track if the function has run const requestId = useRef<number | null>(null);

const token = useSelector((state: AppState) => state.data.token);
const dispatch = useDispatch();

const updateCountdown = () => {
    const newTimeLeft = calculateTimeLeft(dateTime);
    setTimeLeft(newTimeLeft);

    if (
        newTimeLeft.hours === 0 &&
        newTimeLeft.minutes === 0 &&
        newTimeLeft.seconds === 0
    ) {
        if (!hasRunOnce) {
            setHasRunOnce(true); // Mark as run
            (async () => {
                dispatch(getNext15());

                const storedDrawList = localStorage.getItem("drawList");
                if (storedDrawList && token) {
                    const drawList = JSON.parse(storedDrawList);
                    try {
                        await updateExpiredDrawIds(drawList, token);
                    } catch (error: any) {
                        console.log("Error updating draw IDs:", error.message);
                    }
                }
            })();
        }
        return;
    }

    // Schedule the next update
    requestId.current = requestAnimationFrame(updateCountdown);
};

useEffect(() => {
    // Start the countdown
    requestId.current = requestAnimationFrame(updateCountdown);

    // Clean up on unmount
    return () => {
        if (requestId.current) {
            cancelAnimationFrame(requestId.current);
        }
    };
}, [dateTime]);

useEffect(() => {
    const storedToken = localStorage.getItem("token");
    if (storedToken) {
        dispatch(setAccessToken(JSON.parse(storedToken)));
    }
}, [dispatch]);

return (
    <div>
        <span>{String(timeLeft.hours).padStart(2, "0")}:</span>
        <span>{String(timeLeft.minutes).padStart(2, "0")}:</span>
        <span>{String(timeLeft.seconds).padStart(2, "0")}</span>
    </div>
);

};

export default Countdown; `

ndresx commented 1 month ago

Hi, thanks for following up on your initial message. There are a few ways how the countdown should re-render/reset. The guaranteed way is to use the React key prop. For example, you can use the new date value as the key value.

The other ways include updating the date prop, but that doesn't work according to you. I will take another look. The other one is by using the `api´. If you have an example to repo, please consider sharing, thank you.

ndresx commented 2 weeks ago

I'll close this issue. If you have any other thoughts or questions, please feel free to re-open or create a new one!