Не получается создать аналог ComponentDidUnmount в функциональном компоненте

Изучаю работу хука useEffect. Нужно продемонстрировать его работу по типу ComponentDidUnmount, чтобы некий код запускался при размонтировании компонента. Для этого я использую таймер, который запускается с помощью setInterval. В коде есть две ошибки, из-за которых ничего не работает должным образом.

Ошибка 1: не обновляется стейт таймера, он всегда = 0

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

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

export const UseEffect: React.FC = () => {

    const [count, setCount] = useState<number>(0)
    const [intervalTest, setIntervalTest] = useState<NodeJS.Timeout>(
        setTimeout(()=>{})
    )

    useEffect(()=>{

        setIntervalTest(setInterval(()=>{
            setCount(prev => prev + 1)
            console.log(count)
            // TODO: почему не происходит увеличение?
        }, 2000))
    }, [])

    useEffect(()=>{

        return () => {
            clearInterval(intervalTest)
            console.log('таймер count очищен')
        }
    }, [])

    return (
        <>
            </>
        )
    }

Ошибка в консоли после попытки размонтирования:

Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.


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

Автор решения: Konstantin Modin

Можно продемонстрировать например так:

import React, { useState, useEffect } from "react";

const UnmountableComponent = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const id = setInterval(() => setCount(count + 1), 1000);    
    
    return () => {
      clearInterval(id);      
    }
  }, [count, setCount])

  return (
    <>
      <div>I am unmountableComponent</div>
      <div>Count: {count}</div>
    </>
  )
}


export const App = () => {
  const [show, setShow] = useState(true);  

  return (
    <>
      <button onClick={()=>setShow(!show)}>{show ? 'Hide' : 'Show'}</button>
      {show && <UnmountableComponent/>}
    </>
  )
}
export default App;

В useEffect код запскается при монтировании компонента, возвращаемая функция выполняется после размонтирования. Чтобы работал таймер - нужно включить count в массив зависимостей, иначе он не будет обновляться. Работу useEffect можно продемонстрировать и без таймера например так:

import React, { useState, useEffect } from "react";

const UnmountableComponent = () => {
  useEffect(() => {
    console.log('I was mounted!')  
    
    return () => {
      console.log('I was unmounted!')  
    }
  }, [])

  return (   
    <div>I am unmountableComponent</div>  
  )
}


export const App = () => {
  const [show, setShow] = useState(true);  

  return (
    <>
      <button onClick={()=>setShow(!show)}>{show ? 'Hide' : 'Show'}</button>
      {show && <UnmountableComponent/>}
    </>
  )
}
export default App;
→ Ссылка