reactjstypescriptuse-ref

Type for useRef when used with setInterval/clearInterval [react-typescript]


I am setting up a useRef object to hold a NodeJS.Timeout component, the return type of setInterval(). When I use that later in clearInterval, I get an error (shown below) on both instances of intervalRef.current in the code.

What is the correct way to set this up so I can call clearInterval in the button onClick event?

function HookTimer(): ReactElement {

  const [timer, setTimer] = useState(0)

  let intervalRef = useRef<NodeJS.Timeout>()

  useEffect(() => {
    intervalRef.current = setInterval(() => {
      setTimer(prevTimer => prevTimer + 1)
    }, 1000)
    return () => {
      clearInterval(intervalRef.current)
    }
  }, [])

  return (
    <div>
      Hook Timer - {timer}    
      <div>
        <button onClick={() => clearInterval(intervalRef.current)}>Clear Hook Timer</button>
      </div>
    </div>
  )

No overload matches this call. Overload 1 of 2, '(intervalId: Timeout): void', gave the following error. Argument of type 'Timeout | undefined' is not assignable to parameter of type 'Timeout'. Type 'undefined' is not assignable to type 'Timeout'. Overload 2 of 2, '(handle?: number | undefined): void', gave the following error. Argument of type 'Timeout | undefined' is not assignable to parameter of type 'number | undefined'. Type 'Timeout' is not assignable to type 'number'.ts(2769)


Solution

  • const intervalRef = React.useRef<null | NodeJS.Timeout>(null);
    
    React.useEffect(() => {
        intervalRef.current = setInterval(() => {
            console.log('runs every 2 seconds');
        }, 2000);
    
        return () => {
            console.log('clearInterval');
            return clearInterval(intervalRef.current as NodeJS.Timeout);
        };
    }, []);
    

    On initialization intervalRef is set to null.

    setInterval() returns a NodeJS.Timeout type.

    clearInterval(handle) definition says it expects the handle to be of type number | undefined (actually NodeJS.Timeout | undefined). As intervalRef.current could be of type null or NodeJS.Timout (as defined on intervalRef) we need to tell it that the handle should be treated as NodeJS.Timeout.

    Update

    Just for the sake of completeness: A very verbose alternative solution could be a guard:

    // ...
    
    React.useEffect(() => {
        // ...
    
        return () => {
            console.log('clearInterval');
    
            if (intervalRef.current !== null) {
                return clearInterval(intervalRef.current);
            }
        };
    }, []);