skip to Main Content

I have a button that has a callback function. I want the button to start a timeout with a 5 second delay when clicked. If the button is clicked again within that 5 seconds, I want the timeout to reset without calling the timeout handler. Then when 5 seconds elapses, the handler is called.

[timeoutHasInitiated, setTimeoutHasInitiated] = useState(false);

function callbackFunction() {
    if (!timeoutHasInitiated) {
        setTimeoutHasInitiated(true);
        console.log('Button clicked: Timeout Started.');

        const timeout = setTimeout(() => {
            setTimeoutHasInitiated(false);
            console.log('Timeout has Finished.');
          }, 5000);
        return () => clearTimeout(timeout);
    }
    else {
        console.log('Button clicked while timeout in progress');
    }
}

In the code above, clicking the button after the timeout is in progress does nothing.

If I try something like:

function callbackFunction() {
    clearTimeout(timeout);
    const timeout = setTimeout(() => {
        setTimeoutHasInitiated(false);
        console.log('Timeout has Finished.');
      }, 5000);
    return () => clearTimeout(timeout);
}

I get an error:

Block-scoped variable ‘timeout’ used before its declaration.

Variable ‘timeout’ used before it is assigned.

If I try to declare the timeout first before assigning it to setTimeout() like so:

function callbackFunction() {
    let timeout;
    clearTimeout(timeout);
    timeout = setTimeout(() => {
        setTimeoutHasInitiated(false);
        console.log('Timeout has Finished.');
      }, 5000);
    return () => clearTimeout(timeout);
}

The clearTimeout(timeout) called before setTimeout() doesn’t seem to do anything, as multiple timeouts get initiated from multiple button presses.

I suppose I could just disable the button after it is clicked as a workaround, but I was wondering if there was a way to program the desired behavior I described.

2

Answers


  1. If you move your declaration of timeout outside of callbackFunction, it should work as you intend.

    let timeout;
    function callbackFunction() {
        clearTimeout(timeout);
        timeout = setTimeout(() => {
            setTimeoutHasInitiated(false);
            console.log('Timeout has Finished.');
        }, 5000);
        return () => clearTimeout(timeout);
    }
    

    Each call to callbackFunction creates a new execution context so currently each call is accessing its own locally scoped declaration of timeout, which is why you’re seeing multiple timeouts initiate from multiple button presses. Or you could scope timeout to the function by using this.timeout, but being self-taught I’m not sure of the downsides of such an approach.

    function callbackFunction() {
        clearTimeout(this.timeout);
        this.timeout = setTimeout(() => {
            setTimeoutHasInitiated(false);
            console.log('Timeout has Finished.');
        }, 5000); 
        return () => clearTimeout(timeout); 
    }
    
    Login or Signup to reply.
  2. First, you’ll need to move the timeout outside the function like this:

    const timeout = useRef<NodeJS.Timeout>();
    

    Second, you’ll need to change your function like this:

    function callbackFunction() {
      clearTimeout(timeout.current);
      timeout.current = setTimeout(() => {
        console.log("Timeout has Finished.");
      }, 5000);
    }
    

    And finally, you’ll need to globally clear the timeout, like this:

    useEffect(() => {
      return () => clearTimeout(timeout.current);
    }, []);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search