Hello, I am trying to do a rolling countdown where the digit "slides down" once a second is reached. My current implementation is fine, but the problem is that it leaps back (9 -> 0). I am not really sure how to make "be in a loop" where it doesn't leap back. Here is my JSX implementation of the Roller.js component:
import React, { useState, useEffect } from 'react';
import { TransitionGroup, CSSTransition } from 'react-transition-group';
import './Roller.css';
const Roller = ({ value, charList = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"], transition = 0.8 }) => {
const [isRollStart, setIsRollStart] = useState(false);
useEffect(() => {
setTimeout(() => {
setIsRollStart(true);
}, 200);
}, [transition]);
const getIndex = (t, idx) => {
if (!charList.includes(t)) {
if (!t.trim()) return "NULL" + idx;
return String(t) + String(idx);
}
return idx.toString();
};
const findCharIndex = (t, isOriginal = false) => {
let idx = charList.indexOf(t);
if (idx === -1 && !isOriginal) return 0;
return idx;
};
return (
<TransitionGroup className="roller">
{String(value).split("").map((t, idx) => (
t !== '\n'
? (
<CSSTransition key={getIndex(t, idx)} timeout={transition * 1000 + 200} classNames="roller-item">
<div className="roller__wrapper">
{t !== '\n' && (
<ul
className="roller__char rollerBlock"
style={{
top: `${isRollStart ? findCharIndex(t) * -100 : 1}%`,
height: `${charList.length * 100}%`,
transition: `${transition}s`
}}
>
{(findCharIndex(t, true) !== -1 ? charList : [t]).map(char => (
<li
key={char}
className={`roller__char__item ${t === char ? 'copyable' : ''}`}
style={{ opacity: char === ' ' ? 0 : 1 }}
>
{char === " " ? "-" : char}
</li>
))}
</ul>
)}
</div>
</CSSTransition>
) : <br key={idx} />
))}
</TransitionGroup>
);
}
export default Roller;
And my countdown component:
import React, { useEffect, useState } from 'react';
import moment from 'moment';
import Roller from './Roller';
const Countdown = ({ targetDate }) => {
const [timeLeft, setTimeLeft] = useState({
days: 0,
hours: 0,
minutes: 0,
seconds: 0
});
useEffect(() => {
if (!moment.isMoment(targetDate)) {
console.error("Invalid targetDate provided to Countdown.");
return;
}
const interval = setInterval(() => {
const now = moment();
const duration = moment.duration(targetDate.diff(now));
setTimeLeft({
days: duration.days(),
hours: duration.hours(),
minutes: duration.minutes(),
seconds: duration.seconds(),
});
}, 1000);
return () => clearInterval(interval);
}, [targetDate]);
const timeElements = ['days', 'hours', 'minutes', 'seconds'];
return (
<div style={{display: 'inline-flex'}}>
{timeElements.map((element, idx) => {
const paddedTime = String(timeLeft[element]).padStart(2, '0').split('');
return (
<React.Fragment key={element}>
{paddedTime.map((char, charIdx) => (
<Roller key={charIdx} value={char} />
))}
{idx !== timeElements.length - 1 && <span>:</span>}
</React.Fragment>
);
})}
</div>
);
};
export default Countdown;