Managing intervals with useRef

DEMO
Hold to Confirm
setInterval (and setTimeout) is a side effect, so it shouldn't be tied to a component's render method. Instead, it should be in a useEffect. If we want to clear it with an event handler, we can access the interval with useRef.
HoldToConfirm.jsx

const ManagingIntervalWithUseRef = () => {
  const [progress, setProgress] = React.useState(0);
  const [isMouseDown, setIsMouseDown] = useState(false);

  const timerRef = useRef();

  useEffect(() => {
    if (isMouseDown) {
      // cumulatively increase progress every 100ms
      let i = 0;
      timerRef.current = setInterval(() => {
        i += 3;
        setProgress((progress) => {
          if (progress + i >= 100) return 100;
          else return progress + i;
        });
      }, 100);
    } else {
      // keep the bar full if progress is at 100
      setProgress((progress) => (progress >= 100 ? 100 : 0));
      // clear timer if progress didn't hit 100
      clearInterval(timerRef.current);
    }

    // clean-up timer on unmount
    return () => {
      clearInterval(timerRef.current);
    };
  }, [isMouseDown]);

  // reset the button
  useEffect(() => {
    if (isMouseDown && progress === 100) setProgress(0);
  }, [isMouseDown]);

  return (
    <>
      <Progress
        onMouseDown={() => setIsMouseDown(true)}
        onMouseUp={() => setIsMouseDown(false)}
      >
        <Indicator
          style={{ transform: `translateX(-${100 - progress}%)` }}
        />
        <ProgressText>
          {progress === 100 ? (
            <CheckIcon/>
          ) : (
            <>Hold to Confirm</>
          )}
        </ProgressText>
      </Progress>
    </>
  );
}; 

Note: Code snippets do not include styling details unless they are the focus of the exercise.

Copyright © 2022 Explore React