Let's suppose that we built a component which increments its value after clicking on the start button:
jsximport React, { useEffect, useState } from 'react'; const Counter = () => { const [counter, setCounter] = useState(0); useEffect( () => { // On first render, counter will be 0 // The condition will be false and setTimeout() won't start if(counter) setTimeout(() => setCounter(counter + 1), 1000); }, [counter]); const startCounter = () => setCounter(counter + 1); const stopCounter = () => setCounter(0); return( <div className = 'Counter'> {counter} <button onClick = {startCounter}>Start</button> <button onClick = {stopCounter}>Stop</button> </div> ) } export default Counter;
Every time the value of counter
varies, useEffect
Hook will be executed and the value of counter
will increment on every second.
If we click the stop button while the counter is running, we will set the value of counter
to zero. But, because a time out was set and we didn't clear it out, the value of counter won't be zero.
This is an example of a sequence:
t = -1 sec
user clicks on start buttont = 0 sec
counter = 0, setCounter(1) after 1 secondt = 1 sec
counter = 1, setCounter(2) after 1 secondt = 2 sec
counter = 2, setCounter(3) after 1 secondt = 2.5 sec
user clicks on stop button, setCounter(0)t = 3 sec
counter = 3After clicking the stop button, the counter won't be zero. That's a non-desirable behavior.
To avoid this behavior, we need to clear the timer, returning the clearTimeout
function.
jsximport React, { useEffect, useState } from 'react'; const Counter = () => { const [counter, setCounter] = useState(0); useEffect( () => { // On first render, counter will be 0 // The condition will be false and setTimeout() won't start if(counter) var timer = setTimeout(() => setCounter(counter + 1), 1000); return () => clearTimeout(timer); }, [counter]); const startCounter = () => setCounter(counter + 1); const stopCounter = () => setCounter(0); return( <div className = 'Counter'> {counter} <button onClick = {startCounter}>Start</button> <button onClick = {stopCounter}>Stop</button> </div> ) } export default Counter;
So the sequence now will be:
t = -1 sec
user clicks on start buttont = 0 sec
counter = 0, setCounter(1) after 1 secondt = 1 sec
clearTimeout(t = 0), counter = 1, setCounter(2) after 1 secondt = 2 sec
clearTimeout(t = 1), counter = 2, setCounter(3) after 1 secondt = 2.5 sec
user clicks on stop button, setCounter(0)t = 3 sec
clearTimeout(t = 2), counter = 0Notice how clearTimeout()
is executed just before there is a change in the counter, canceling the previous setTimeout()
. Now the component works as expected.
Hi, I'm Erik, an engineer from Barcelona. If you like the post or have any comments, say hi.