skip to Main Content

I got a strange behaviour in my react code:

In development (strict) mode, the following component did not clear interval successfully in clear function when interval is set to 1000 milliseconds. If I change the interval to slightly larger value (e.g. 1010 milliseconds). The callback can be stopped when component unmount. I have no ideas why the code behave like this. Anyone knows why it have such strange behaviour and how should I fix it?

function Timer() {
  const [secondsPassed, setSecondPassed] = useState(0);
  const [rows, setRow] = useState(1);
  const thresold = 60;

  useEffect(() => {
    console.log("effect start");

    function onInterval() {
      setSecondPassed(prevSec => {
        let currentSecond = prevSec + 1;
        console.log(currentSecond);
        if (currentSecond % thresold === 0) {
          setRow(prevRow => prevRow + 1);
        }
        return currentSecond;
      });
    }
    const intervalId = setInterval(onInterval, 1000);
    console.log(`setinterval: ${intervalId}`);
    
    return () => {
      console.log(`clearInterval: ${intervalId}`);
      clearInterval(intervalId);
    }
  }, []);

  return (
    <>
      <div className="box-x" style={{ width: secondsPassed % thresold }}></div>
    </>
  );
}

However, this clock component works in the same app just fine.

function Clock() {
  const [now, setNow] = useState(new Date());
  
  useEffect(() => {
    const intervalId = setInterval(() => {
      setNow(new Date());
    }, 1000);
    
    return () => {
      clearInterval(intervalId);
    }
  }, []);

  return (<h2 >Time now is {now.getSeconds()}</h2>);
}

If I change to use setTimeout() and setTimeout() instead, it works fine with 1000 milliseconds as well.

Environment:

  • Node: v22.3.0
  • React: 18.3.1
  • vscode: 1.93.1
  • Chrome: 127.0.6533.120 arm64

2

Answers


  1. Please see the dependency array, it now is empty. It means the code inside the hook will run only on two events – mount and unmount of the components. The code inside the hook will run on mount, and the code returned by this code will run on unmount. That is the reason the clearInterval now works only once. However this code works fine and gives you the correct results.

     useEffect(() => {
        console.log("effect start");
        ...
      }, []);
    

    If you still want to see how to force a clearInterval, please see below one of the ways for doing it.

    Steps to do:

    a) Please add a dependency to the hook.

    b) The onInterval function has to move out of the hook.
    And then set it as its dependency.

    c) What would now happen is that on each render, the function onInterval
    will be re-defined and will result a new functional object.

    d) This will cause the dependency in the hook to be identified as a
    change.

    e) And that will lead to a re-synchronise action.

    f) Every re-synchronise action will start with the execution of the
    returned function and then proceed with the handler of the hook.

    App.js

    import { useEffect, useState } from 'react';
    
    export default function Timer() {
      const [secondsPassed, setSecondPassed] = useState(0);
      const [rows, setRow] = useState(1);
      const thresold = 60;
    
      function onInterval() {
        setSecondPassed((prevSec) => {
          let currentSecond = prevSec + 1;
          console.log(currentSecond);
          if (currentSecond % thresold === 0) {
            setRow((prevRow) => prevRow + 1);
          }
          return currentSecond;
        });
      }
    
      useEffect(() => {
        console.log('effect start');
    
        const intervalId = setInterval(onInterval, 1000);
        console.log(`setinterval: ${intervalId}`);
    
        return () => {
          console.log(`clearInterval: ${intervalId}`);
          clearInterval(intervalId);
        };
      }, [onInterval]);
    
      return (
        <>
          <div className="box-x" style={{ width: secondsPassed % thresold }}>
            seconds passed : {secondsPassed}
          </div>
        </>
      );
    }
    

    Test run

    Browser display

    Login or Signup to reply.
  2. As it is in the another answer, the useEffect hook in the following code also works only on two events because of the empty dependency array. You may please check and see, the statement console.log(‘clearInterval’) executes only on unmount – you can mock that event by page refresh)

    import { useState, useEffect } from 'react';
    
    export default function Clock() {
      const [now, setNow] = useState(new Date());
    
      useEffect(() => {
        const intervalId = setInterval(() => {
          setNow(new Date());
        }, 1000);
    
        return () => {
          console.log('clearInterval');
          clearInterval(intervalId);
        };
      }, []);
    
      return <h2>Time now is {now.getSeconds()}</h2>;
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search