0

I have the following state variable

const [value, setValue] = useState(0);

and useEffect hook

let intervalID;
useEffect(() => {
  intervalID = setInterval(() => {
    setValue(value => value+1);
  }, 100)
}, [])

I want the setInterval function to stop when value reaches 100; How can I achieve this?

I tried if(value < 100 ) clearInterval(intervalID) inside setInterval but it didn't work.

BlackMath
  • 932
  • 7
  • 24

3 Answers3

1

That's because your intervalID is resetting on every state update. you need to store intervalId in ref so that it doesn't changes between state updates.

export default function App() {
  const [value, setValue] = useState(0);

  // store timeoutID in ref, initialy it's null
  const timeoutID = useRef(null);
  useEffect(() => {
    // store timeoutID in ref current property
    timeoutID.current = setTimeout(() => {
      if (value > 100) {
        // clear interval
        clearTimeout(timeoutID.current);
        timeoutID.current = null;
        return;
      }
      setValue((value) => value + 1);
    }, 1000);

    // clear interval if component unmounts before value reaches 100
    return () => clearTimeout(timeoutID.current);
  }, [value]);
  return value;
}


Gulam Hussain
  • 1,591
  • 10
  • 19
1

This problem requires keen observation and know-how of the working of useEffect. Okay let's break this down, let's consider your code and see why it doesn't work:

let intervalID;
useEffect(() => {
  intervalID = setInterval(() => {
    setValue(value => value+1);
  }, 100)
}, [])

We can notice that the dependency array is empty, meaning useEffect runs only once when it is mounted. So right after the component is mounted, your useEffect runs and the setInterval is triggered to increment the value every 100ms. Since there are no conditions given when to stop, this keeps going on forever. Further, you are not making use of the current property on the intervalId instance (Although it is not mandatory, it's a good practice).

In order to get this working, we'd want to add some conditions to stop the interval after the value reaches 100. This condition can be checked when useEffect is invoked every time after the value is incremented. To invoke useEffect when value is incremented, we can add value as a dependency array. Now that we're invoking the function, the next part is fairly simple and that is, checking if the value has reached 100 or not, this could be done using a simple if condition. Since we've added a dependency array to useEffect and it runs whenever value changes, it is important to clean up the previously created intervals, using clearInterval. clearInterval should be invoked before creating a new interval, and the best time to clear the interval is when it is unmounting. So all-in-all you could do something like this:

export default function App() {
  const [value, setValue] = useState(0);
  let intervalID = useRef(null); //using useRef hook to maintain state changes across rerenders.
  useEffect(() => {
    
    if (value < 100) {
      intervalID.current = setInterval(() => { //Proceed to set interval if value is less than 100
        setValue((value) => value + 1);

      }, 100);
    } 
    

    return () => clearInterval(intervalID.current); //clear previous interval when unmounting.
  }, [value]);
  
  
  return (
    <div className="App">
      <h1>Hello{value}</h1>
    </div>
  );
}

Since an interval is created and destroyed during each mounting/unmounting phase, you could alternatively use setTimeout also, which again would give the same result.

Prajwal Kulkarni
  • 1,480
  • 13
  • 22
0
const [value, setValue] = useState(0);

const intervalID = useRef(null);
useEffect(() => {
    intervalID.current = setInterval(() => {
        if (value < 3) {
            console.log('Set Interval')
            setValue(value => value + 1);
        }
        else
        {
            console.log('Clear Interval')
            clearInterval(intervalID.current);
            intervalID.current = null;
        }
    }, 1000);
    
    return () => clearInterval(intervalID.current)

}, [value])

return (
    <>Interval Value: {value}</>
)