ClearInterval is not working as expected with useRef in React

Viewed 42

I have created one setInterval which is being called on App Load, Like this

  let abc = true;
  let def = true;
  const intervalRef = useRef(null);

  if (abc) {
    intervalRef.current = setInterval(() => {
      console.log("hello console", def);
    }, 2000);
    abc = false;
  }

And trying to stop this interval using clearInterval on some click handler like this -

const clear = () => {
    clearTimeout(intervalRef.current);
    def = false;
    console.log(def);
  };

Issues I am facing right now are -

  1. Why SetInterval is not stopped on click handler, while I am already cleared it in function?
  2. Why value of def in not getting updated in setInterval.

Working example

PS: I am new in react, if this is silly mistake forgive me :)

2 Answers

this is not a silly question, it's something I have seen many people struggling with. Let me clarify this:

  1. Why SetInterval is not stopped on the click handler, while I am already cleared it in function?

This is happening because your component is rendering more than once (so you are creating multiple intervals and only clearing the last one.

  1. Why the value of def is not getting updated in setInterval.

There are 2 things to consider here, first, the setInterval callback is not included in react lifecycle so the value does not get updated. Last, since you are only changing the value of a variable (but not the component state) the component will not be re-rendered and the code will remain the same.

You can find a very good article to deal with intervals in react here -> https://overreacted.io/making-setinterval-declarative-with-react-hooks/

I also leave you the code here as well as a code snippet link:

import "./styles.css";
import { useEffect, useRef, useState } from "react";
export default function App() {
  const [runInterval, setRunInterval] = useState(true);
  const [counter, setCounter] = useState(0);
  const intervalRef = useRef(null);

  useEffect(() => {
    if (runInterval) {
      intervalRef.current = setInterval(() => {
        setCounter((c) => c + 1);
      }, 1000);
    }

    return () => {
      clearInterval(intervalRef.current);
    }; // clear interval when unmounting the component
  }, [runInterval]);

  const clear = () => {
    setRunInterval(false);
  };

  return (
    <div className="App">
      <h1 onClick={() => clear()}>Click me to Stop Interval</h1>
      <h2>{counter}</h2>
    </div>
  );
}

https://codesandbox.io/s/stoic-elbakyan-u5ezc3?file=/src/App.js:0-734

  1. Why SetInterval is not stopped on click handler, while I am already cleared it in function?

    One reason is that it should be clearInterval instead of clearTimeout.

  2. Why value of def in not getting updated in setInterval.

    You should use useState hook to handle state in a component. Otherwise abc value gets newly created for each render o(when calling App) falling back to the initial value.

You need a useEffect hook to handle it like below. If someone visits this component again and again and did not click clear then it will be a memory leak (since multiple intervals get cleated and never get distroyed). Need to handle it clearly when the component gets unmounted as well.

import { useRef, useEffect } from "react";
export default function App() {
  const intervalRef = useRef(null);

  useEffect(() => {
    const intervalId = setInterval(() => {
      console.log("hello JSG");
    }, 1000);
    // save the interval
    intervalRef.current = intervalId;
    // clear the interval
    return () => {
      clearInterval(intervalRef.current);
    };
  }, []);

  const clear = () => {
    // clear the interval
    clearInterval(intervalRef.current);
  };

  return (
    <div className="App">
      <h1 onClick={() => clear()}>Click me to Stop Interval</h1>
      <h2>Please watch console!</h2>
    </div>
  );
}

Working example:

Edit pedantic-hypatia-uvuonk

Related