Обратный отсчет времени React

Я сделал таймер обратного отсчета.

const { useState,useEffect } = React;

const App = (props) => {

    const hour = props.date.getHours();
    const min = props.date.getMinutes();
    const sec = props.date.getSeconds();
    const [over, setOver] = useState(false);
    const [[h, m, s], setTime] = useState([hour, min, sec]);

    const tick =()=>{
        if(over) return;
        if (h === 0 && m === 0 && s === 0) {
            setOver(true);
        } else if (m === 0 && s === 0) {
            setTime([h - 1, 59, 59]);
        } else if (s == 0) {
            setTime([h, m - 1, 59]);
        } else {
            setTime([h, m, s - 1]);
        }
    }

    useEffect(()=>{
        const timerID = setInterval(() => tick(), 1000);
        return () => clearInterval(timerID);
    })

  return ( 
            <p>{`${h.toString().padStart(2, '0')}:${m
                .toString()
                .padStart(2, '0')}:${s.toString().padStart(2, '0')}`}</p>
  )
}

ReactDOM.render(<App date={new Date('Thu, 26 Sep 2022 22:00:00')}/>, document.getElementById("root"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Все работает хорошо. Но мне надо чтобы он показывал оставшееся время не от переданного в props. А от разницы между временем в props и текущем временем. Не могу понять как правильно передать разницу времени в компанент


Ответы (1 шт):

Автор решения: Daniil Loban

Наиболее подходящим на мой взгляд в React, использовать useEffect с отслеживанием tick и расчитывать разницу из миллисекунд. Передаем дату финиша, получаем оставшееся время, и экономим вызовы setState

Что касается дней, то наверно лучше показывать в зависимости от того сколько отрезков в 24 часа помещается, если меньше то смысла нет показывать.

Вариант 1

const { useState,useEffect } = React;
  const App = ({date}) => {
    const [finishTime] = useState(date.getTime());
    const [[diffDays, diffH, diffM, diffS], setDiff] = useState([0, 0, 0, 0]);
    const [tick, setTick] = useState(false);

    useEffect(()=> {
      const diff = (finishTime - new Date()) / 1000;
      if (diff < 0) return // время вышло
      setDiff([
        Math.floor(diff / 86400), // дни
        Math.floor((diff / 3600) % 24), 
        Math.floor((diff / 60) % 60), 
        Math.floor(diff % 60)
      ]) 
    }, [tick, finishTime])
        
    useEffect(()=>{
      const timerID = setInterval(() => setTick(!tick), 1000);
      return () => clearInterval(timerID);
    }, [tick])

    return ( 
      <p>{`${diffDays} дней ${diffH.toString().padStart(2, '0')}:${diffM
          .toString()
          .padStart(2, '0')}:${diffS.toString().padStart(2, '0')}`}</p>
    )
  }

ReactDOM.render(<App date={new Date('Wed, 5 Oct 2022 00:00:00')}/>, document.getElementById("root"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Вариант 2 (расширенный)

Так же было бы не плохо остановить таймер, при достижении таймаута, чтобы не тратить ресурсы браузера впустую, поэтому в следующем снипете я вынес в локально состояние isTimeout и timerId и добавил еще один useEffect:

const { useState,useEffect } = React;

const App = ({ date }) => {
  const [finishTime] = useState(date.getTime());
  const [[diffDays, diffH, diffM, diffS], setDiff] = useState([0, 0, 0, 0]);
  const [tick, setTick] = useState(false);
  const [isTimeout, setIsTimeout] = useState(false);
  const [timerId, setTimerID] = useState(0);

  useEffect(() => {
    const diff = (finishTime - new Date()) / 1000;
    if (diff < 0) {
      setIsTimeout(true);
      return;
    }
    setDiff([
      Math.floor(diff / 86400), // дни
      Math.floor((diff / 3600) % 24),
      Math.floor((diff / 60) % 60),
      Math.floor(diff % 60)
    ]);
  }, [tick, finishTime]);

  useEffect(() => {
    if (isTimeout) clearInterval(timerId);
  }, [isTimeout, timerId]);

  useEffect(() => {
    const timerID = setInterval(() => {
      setTick(!tick);
    }, 1000);
    setTimerID(timerID);
    return () => clearInterval(timerID);
  }, [tick]);

  return (
    <p>{`${diffDays} дней ${diffH
      .toString()
      .padStart(2, "0")}:${diffM
      .toString()
      .padStart(2, "0")}:${diffS.toString().padStart(2, "0")}`}</p>
  );
};

ReactDOM.render(<App date={new Date('Wed, 5 Oct 2022 00:00:00')}/>, document.getElementById("root"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>

→ Ссылка