setInterval в реакции не обновляет setState

Хочу сделать таймер обратного отсчета (25 минут). Это код в файле App.js

import './App.css';
import React, { useState } from 'react';

function App() {
  const [remainingTime, setRemainingTime] = useState(1500);
  const [seconds, setSeconds] = useState(0);
  const [minute, setMinute] = useState(remainingTime/60);

  function timer() {
    setRemainingTime(remainingTime-1);
    let newMinute = remainingTime/60;
    let minuteArray = [...newMinute.toString()];
    setMinute(parseInt(minuteArray.slice(0,2).join("")));
    setSeconds(remainingTime%60);
    console.log(minute);
    console.log(seconds);
  }

  return (
    <div className="App">
      <div className="pomodoro">
        <div className="timer">{minute}:{seconds}</div>
        <div className="button-container">
          <button className="start" onClick={() => setInterval(timer, 1000)}>start</button>
        </div>
      </div>
    </div>
  );
}

export default App;

Интервал не обновляет значение состояния. Значение минут всегда 25, а значение секунд всегда 0. Когда я не использую setInterval и просто использую функцию таймера, подобную этой

<button className="start" onClick={timer}>start</button>

каждый раз, когда я нажимаю кнопку «Пуск», значение меняется. Любая идея? Я знаю, что мне тоже следует использовать clearInterval, но я не знаю, куда его поместить. Должен ли я создать новую функцию, содержащую setInterval и clearInterval?


person Yustina Yasin    schedule 26.04.2021    source источник


Ответы (2)


Проблемы

Основная проблема - устаревшие вложения состояния в обратном вызове timer.

Похоже, вы сделали это немного сложнее, чем нужно.

Решение

  1. minute и seconds считаются производными состояниями (от remainingTime), поэтому они не должны также храниться в состоянии, они легко вычисляются из состояния.
  2. Используйте обновление функционального состояния, чтобы обновить состояние remainingTime из предыдущего состояния, а не состояние цикла рендеринга, в котором обратный вызов был поставлен в очередь.
  3. Используйте React ref для хранения ссылки интервального таймера, чтобы интервал можно было очистить.
  4. Используйте ловушку useEffect, чтобы вернуть функцию очистки, чтобы очистить любые рабочие интервалы при размонтировании компонента.

Код:

function App() {
  const timerRef = React.useRef();

  React.useEffect(() => {
    return () => clearInterval(timerRef.current);
  }, []);

  const [remainingTime, setRemainingTime] = React.useState(1500);

  function timer() {
    setRemainingTime((remainingTime) => remainingTime - 1);
  }

  const startTimer = () => {
    clearInterval(timerRef.current);             // clear any running interval
    setRemainingTime(1500);                      // reset state back to 25 minutes
    timerRef.current = setInterval(timer, 1000); // start/restart interval
  };

  const minute = String(Math.floor(remainingTime / 60)).padStart(2, 0);
  const seconds = String(remainingTime % 60).padStart(2, 0);

  return (
    <div className="App">
      <div className="pomodoro">
        <div className="timer">
          {minute}:{seconds}
        </div>
        <div className="button-container">
          <button className="start" onClick={startTimer}>
            start
          </button>
        </div>
      </div>
    </div>
  );
}

Демо

введите описание изображения здесь

 Изменить setinterval-in-react-does-not-update-the-setstate

Поскольку это, вероятно, будет последующий вопрос, используйте другой useEffect, чтобы прослушать состояние remainingTime, и когда время достигнет 0, очистите интервал, сбросьте remainingTime назад 1500 (для отображения) и покажите все будильник или оповещение или что-то еще для перерыва / расписания pomodoro.

person Drew Reese    schedule 26.04.2021

При каждом повторном рендеринге таймер устанавливается на 1500. Используйте здесь закрытие, активируемое щелчком.

function timer() {
    let t = remainingTime;
    setInterval(() => {
      t -= 1;
      setRemainingTime(t);
      let newMinute = t / 60;
      let minuteArray = [...newMinute.toString()];
      setMinute(parseInt(minuteArray.slice(0, 2).join("")));
      setSeconds(t % 60);
      console.log(minute);
      console.log(seconds);
    }, 1000); } 
person Achal Jain    schedule 26.04.2021