Ashar
Ashar

Reputation: 774

React: Setinterval is not stopping even after clearing the timer using clearInterval

I have made a basic slideshow demo where on checking the checkbox slideshow is enabled. Problem is that once enabled slide show can't be disabled even if I uncheck the checkbox. As per muy understanding, I'm learning the timer and also nullifying the state storing the timer but the slide show keep on going.

Specifically this part gets invoked on checkbox update:

useEffect(() => {
  if (isTrue) {
    setSlideTimer(() => {
      return setInterval(() => {
        forwardButton.current.click();
      }, slideDuration);
    });
  } else {
    clearInterval(slideTimer);
    setSlideTimer(null);
  }
}, [isTrue]);

From browser logs it is evident that timer indeed got cleared. Though there is a warning "... component is changing an uncontrolled input of type checkbox to be controlled" but I'm not sure if that's the culprit here.

Upvotes: 2

Views: 821

Answers (3)

Drew Reese
Drew Reese

Reputation: 202801

Issue

The issue is that you've a missing dependency on the sliderTimer state.

useEffect(() => {
    if (isTrue) {
      setSlideTimer(() => {
        return setInterval(() => {
          forwardButton.current.click();
        }, slideDuration);
      });
    } else {
      clearInterval(slideTimer);
      setSlideTimer(null);
    }
  }, [isTrue]);

Solution

Don't generally store timer ids in state. Use a React ref is you need to access the timer id outside the useEffect hook, otherwise just cache it locally within the useEffect hook's callback. In this case you will want to use a ref to hold the sliderTimer id value so it can also be cleared in the case the component unmounts.

Example:

const sliderTimerRef = React.useRef();

useEffect(() => {
  // Clear any running intervals on component unmount
  return () => clearInterval(sliderTimerRef.current);
}, []);

useEffect(() => {
  if (isTrue && forwardButton.current) {
    sliderTimerRef.current = setInterval(
      forwardButton.current.click,
      slideDuration
    );
  } else {
    clearInterval(sliderTimerRef.current);
  }
}, [isTrue]);

Additional issue

From browser logs it is evident that timer indeed got cleared. Though there is a warning "... component is changing an uncontrolled input of type checkbox to be controlled" but I'm not sure if that's the culprit here.

This is typically the case when the value or checked prop changes from an undefined to a defined value. Ensure whatever the checked state is that it is initially defined, even if just false.

Upvotes: 3

Gass
Gass

Reputation: 9344

Another approach is to use the setTimeout() method. For this you would need to create a new state clicks (or any other name) that will trigger the useEffect every time it changes, allowing setTimeout to work as setInterval

const [clicks, setClicks] = useState(0)

useEffect(() => {
  if(isChecked){
    setTimeout(() => { 
      forwardButton.current.click()
      setClicks(clicks + 1)
    }, slideDuration)
  }
}, [isChecked, clicks])

Upvotes: 0

Abbas Hussain
Abbas Hussain

Reputation: 1395

Try this!

  useEffect(() => {
    let interval;
    if (isChecked) {
      interval = setInterval(() => {
        forwardButton.current.click();
      }, 1000); // change it
    }

    return function cleanup() {
      clearInterval(interval);
      console.log('Clear timer');
    };
  }, [isChecked]);

Upvotes: 1

Related Questions