#Rolling countdown leaps back to start instead of doing a continous roll

2 messages · Page 1 of 1 (latest)

acoustic notch
#

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;
static sandalBOT
#

🔎 This post has been indexed in our web forum and will be seen by search engines so other users can find it outside Discord

🕵️ Your user profile is private by default and won't be visible to users outside Discord, if you want to be visible in the web forum you can add the "Public Profile" role in id:customize

✅ You can mark a message as the answer for your post with Right click -> Apps -> Mark Solution
(if you don't see the option, try refreshing Discord with Ctrl + R)