React hooks - правильный способ очистить тайм-ауты и интервалы

Я не понимаю, почему, когда я использую функцию setTimeout мой компонент реакции запускается в бесконечный console.log. Все работает, но ПК начинают адить как ад. Некоторые люди говорят, что эта функция во время ожидания изменяет мое состояние и этот компонент рендеринга, который устанавливает новый таймер и так далее. Теперь мне нужно понять, как это правильно очистить.

export default function Loading() {
  // if data fetching is slow, after 1 sec i will show some loading animation
  const [showLoading, setShowLoading] = useState(true)
  let timer1 = setTimeout(() => setShowLoading(true), 1000)

  console.log('this message will render  every second')
  return 1
}

Очистить в другой версии кода не помогает:

const [showLoading, setShowLoading] = useState(true)
  let timer1 = setTimeout(() => setShowLoading(true), 1000)
  useEffect(
    () => {
      return () => {
        clearTimeout(timer1)
      }
    },
    [showLoading]
  )

Ответы

Ответ 1

Функция возврата в useEffect запускается каждый раз, когда запускается useEffect (кроме первого запуска при монтировании компонента). Подумайте об этом, так как каждый раз, когда происходит новое выполнение useEffect, старое удаляется.

Это рабочий способ использования и очистки тайм-аутов или интервалов:

export default function Loading() {   
     const [showLoading, setShowLoading] = useState(false)

     useEffect(
        () => {
          let timer1 = setTimeout(() => setShowLoading(true), 1000)

          // this will clear Timeout when component unmont like in willComponentUnmount
          return () => {
            clearTimeout(timer1)
          }
        },
        [] //useEffect will run only one time
           //if you pass a value to array, like this [data] than clearTimeout will run every time this value changes (useEffect re-run)
      )

 return showLoading && <div>I will be visible after ~1000ms</div>
}

Если вам нужно очистить тайм-ауты или интервалы где-то за пределами:

export default function Loading() {   
     const [showLoading, setShowLoading] = useState(false)

     const timerToClearSomewhere = useRef(false) //now you can pass timer to another component

     useEffect(
        () => {
          timerToClearSomewhere.current = setInterval(() => setShowLoading(true), 1000)

          return () => {
            clearInterval(timerToClearSomewhere.current)
          }
        },
        []
      )

  //here we can imitate clear from somewhere else place
  useEffect(() => {
    setTimeout(() => clearInterval(timerToClearSomewhere.current), 15000)
  }, [])

 return showLoading && <div>I will be visible after ~1000ms</div>
}

Если вам нужно управлять очередью (изменить состояние внутри таймера/интервала), ищите мой ответ здесь.

Ответ 2

Ваш компьютер зависал, потому что вы, вероятно, забыли передать пустой массив в качестве второго аргумента useEffect и setState в setState. Это вызывает бесконечный цикл, потому что useEffect срабатывает при рендеринге.

Вот рабочий способ установки таймера при монтировании и очистки его при размонтировании:

function App() {
  React.useEffect(() => {
    const timer = window.setInterval(() => {
      console.log('1 second has passed');
    }, 1000);
    return () => { // Return callback to run on unmount.
      window.clearInterval(timer);
    };
  }, []); // Pass in empty array to run useEffect only on mount.

  return (
    <div>
      Timer Example
    </div>
  );
}

ReactDOM.render(
  <div>
    <App />
  </div>,
  document.querySelector("#app")
);
<script src="https://unpkg.com/[email protected]/umd/react.development.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script>

<div id="app"></div>