Как остановить setTimeout вызвав функцию остановки stop() в useEffect?

Как остановить setTimeout вызвав функцию остановки stop() в useEffect?

function App() {
  const [value, setValue] = useState(0)
  const [isStart, setIsStart] = useState(false)

  useEffect(() => {
    stop()
  }, [value])

  let timeoutId = null;

  function start() {
    console.log('start')
    if (timeoutId) {
      return;
    }
    timeoutId = setTimeout(() => {
      console.log('timeout')
      setIsStart(true)
    }, 5000);
  }

  function stop() {
    if (timeoutId) {
      console.log('stop')
      setIsStart(false)
      clearTimeout(timeoutId);
      timeoutId = null;
    }
  }

  return (
    <div className="App">
      <button onClick={() => setValue(value + 1)}>value+1</button>
      <button onClick={() => start()}>OK</button>
      <div>
        {`value: ${value}`}
      </div>
      <div>
        {isStart ? 'Start' : 'Nope'}
      </div>
    </div>
  );
}

export default App;

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

Автор решения: Yakov Botov

Для того, чтобы у вас успешно получилось остановить timeout в useEffect, при изменении какого-то состояния, сам timeoutId должен быть сохранен не просто в виде переменной, а в виде ref'a react. Для этого в функциональной компоненте можно использовать хук useRef.

Ваш код работал некорректно, только лишь потому, что вы теряли ссылку на timeoutId после рендера компонента. Все дело в том, что когда рендерится функциональный реакт компонент весь код внутри него выполняется снова и снова, т.е. если просто положить значение внутри компонента в переменную const someValue = {val: 1} - то это значение будет присваиваться каждый рендер новое

Ссылка на раздел документации о useRef: https://ru.reactjs.org/docs/hooks-reference.html#useref Ссылка на вопросы о переменных в реакт компонентах: https://ru.reactjs.org/docs/hooks-faq.html#is-there-something-like-instance-variables

Исправленный вариант кода:

function App() {
  const [value, setValue] = useState(0);
  const [isStart, setIsStart] = useState(false);

  useEffect(() => {
    stop();
  }, [value]);

  /**
   * Для сохранения значения в переменной
   * между рендерами компонента, можно использовать useRef.
   * Таким образом, мы не потеряем, при обновлени компонента,
   * данные о timeout
   */
  let timeoutRef = useRef(null);

  function start() {
    console.log("start");
    if (timeoutRef.current) {
      return;
    }
    timeoutRef.current = setTimeout(() => {
      console.log("timeout");
      setIsStart(true);
    }, 5000);
  }

  function stop() {
    if (timeoutRef.current) {
      console.log("stop");
      setIsStart(false);
      clearTimeout(timeoutRef.current);
      timeoutRef.current = null;
    }
  }

  return (
    <div className="App">
      <button onClick={() => setValue(value + 1)}>value+1</button>
      <button onClick={() => start()}>OK</button>
      <div>{`value: ${value}`}</div>
      <div>{isStart ? "Start" : "Nope"}</div>
    </div>
  );
}
→ Ссылка