Не работает clearInterval

const Timer = () => {
  var timerId = 0;
  let [timer, setTimer] = useState(10);
  useEffect(() => {
    if (timer === 0) {
      clearInterval(timerId);
    }
  }, [timer]);

  timerId = setInterval(updateCoutDown, 1000);
  function updateCoutDown() {
    setTimer((timer = timer - 1));
  }
  return (
    <div>
      <h1>{timer}</h1>
    </div>
  );
};

Проблема заключается в том что clearInterval не работает. Читал похожие проблемы, ничего не помогло


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

Автор решения: Node_pro

Попробуйте так. У Вас переменная timerId - постоянно обнулялась и соответсвенно была проблема. Код немного поправил

import React, {useState, useEffect, useRef} from 'react';    

const Timer = () => {
  const timerId = useRef();
  const [timer, setTimer] = useState(10);

  useEffect(()=>{
     timerId.current = setInterval(() => 
         setTimer(prev => prev-1)
     , 1000);
  }, []);

  useEffect(() => {
    if (timer === 0) {
      clearInterval(timerId.current);
    }
  }, [timer]);

  return (
    <div>
      <h1>{timer}</h1>
    </div>
  );
};
→ Ссылка
Автор решения: Grundy

clearInterval - работает. Просто запускается много setInterval, а останавливается только один.

Чтобы запускался только один, его можно запускать внутри useEffect с пустым массивом зависимостей.

В этом случае, однако, id таймера нужно хранить в стейте, чтобы можно было остановить его не только в момент выгрузки компонента

const {
  useState,
  useEffect
} = React;
const Timer = () => {
  var [timerId, setTimerId] = useState(0);
  let [timer, setTimer] = useState(10);
  useEffect(() => {
    if (timer === 0) {
      clearInterval(timerId);
      console.log('timer stop');
    }
  }, [timer, timerId]);

  useEffect(() => {
    let internalTimerId = setInterval(updateCoutDown, 1000);

    function updateCoutDown() {
      setTimer(prev => prev - 1);
      console.log('timer hit');
    }
    setTimerId(internalTimerId)
    return () => clearInterval(internalTimerId);
  }, [])
  return ( <
    div >
    <
    h1 > {
      timer
    } < /h1> <
    /div>
  );
};

ReactDOM.createRoot(root).render( < Timer / > )
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>

→ Ссылка
Автор решения: EzioMercer

Замечания:

  • Не стоит использовать var, вместо него используйте let
  • НИ В КОЕМ СЛУЧАЕ не мутируйте стейт компонента. Чтобы избежать такого рода ошибок стейты объявляют через const, а не let
  • Если внутри useEffect-а используете таймеры, то не забывайте прописывать очищение таймера при удалении компонента

Решения:

  1. На самом деле в вашей задаче нет никакой нужды в setInterval-е. Задача намного легче решается если использовать setTimeout:

    1. Пишем useEffect, который будет слушать timer

    2. Внутри него каждый раз объявляем новый setTimeout, который очищаем сразу при достижении 0

    Плюс такого подхода в том, что нам не нужно думать об очищении таймера после каждой секунды. Мы следим только за timer-ом

    const {useState, useEffect} = React;
    
    const Timer = () => {
      const [timer, setTimer] = useState(10);
      
      useEffect(() => {
        const timerId = setTimeout(updateCountDown, 1000);
        
        if (timer === 0) clearTimeout(timerId);
        
        return () => clearTimeout(timerId);
      }, [timer]);
      
      function updateCountDown() {
        setTimer(timer => timer - 1);
      }
      
      return (
        <div>
          <h1>{timer}</h1>
        </div>
      );
    };
    
    ReactDOM.createRoot(root).render(<Timer />);
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.production.min.js"></script>
    <div id="root"></div>

  2. Если вам всё же необходимо использовать именно setInterval, тогда можно сделать так:

    1. Объявляем timerId с помощью useRef, потому что нам не нужно будет его менять между перерисовками. Всё что нам нужно - это сохранить идентификатор таймера даже после перерисовки

    2. Пишем useEffect, который запустим лишь один раз - при монтировании компонента. Таким образом мы избегам случая, когда у нас создаётся множество таймеров

    3. Далее остаётся лишь проверять наш счётчик. При достижении 0 мы очищаем таймер

    const {useState, useEffect, useRef} = React;
    
    const Timer = () => {
      const timerId = useRef(null);
      const [timer, setTimer] = useState(10);
      
      useEffect(() => {
        timerId.current = setInterval(updateCountDown, 1000);
        
        return () => clearTimeout(timerId.current);
      }, []);
      
      if (timer === 0) clearTimeout(timerId.current);
      
      function updateCountDown() {
        setTimer(timer => timer - 1);
      }
      
      return (
        <div>
          <h1>{timer}</h1>
        </div>
      );
    };
    
    ReactDOM.createRoot(root).render(<Timer />);
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.production.min.js"></script>
    <div id="root"></div>

→ Ссылка