Progressbar re renders previous state when i reset the state

Viewed 140

I have a progresbar that receives a value as the width When I click the "Reset" button it does reset the state to 0 and works as expected However when I click the Reset button before the progressbar is 100% width it will continue increasing the state

How can i prevent this from happening?

import React, { useEffect, useState } from "react";

function ProgressBar({ value = 0 }) {
  const [percent, setPercent] = useState(value);
  useEffect(() => {
    setPercent(value);
  }, [value]);
  return (
    <>
      {value.toFixed(2)}%
      <div
        style={{ display: "flex", width: "100%", border: "1px solid black" }}
      >
        <div
          style={{
            border: "1px solid black",
            backgroundColor: "red",
            height: "0.5rem",
            transition: 0,
            width: `${percent}%`
          }}
        ></div>
      </div>
    </>
  );
}

export default function App() {
  const [percent, setPercent] = useState(0);
  const [willReset, setWillReset] = useState(false);

  const step = 100 / 3;

  useEffect(() => {
    function updateProgress() {
      if (!willReset) {
        setTimeout(() => {
          if (percent + step > 100) {
            setPercent(percent + (100 - percent));
          } else {
            setPercent(percent + step);
          }
        }, 1000);
      } else {
        setPercent(0);
      }
    }
    updateProgress();
  }, [percent, willReset]);

  return (
    <div className="App">
      <button
        onClick={() => {
          setWillReset(true);
        }}
      >
        Reset
      </button>
      {percent !== 100 && <ProgressBar value={percent} />}
    </div>
  );
}
1 Answers

The reason this happens is because you are using setTimeout inside of your useEffect hook. You must return a cleanup function from your useEffect callback to clear the setTimeout or else it will continue to be called.

I would write it like this:

useEffect(() => {
  if (willReset) {
    setPercent(0);
    return;
  }

  const id = setTimeout(() => {
    if (percent + step > 100) {
      setPercent(percent + (100 - percent));
    } else {
      setPercent(percent + step);
    }
  }, 1000);

  return () => clearTimeout(id);
}, [percent, willReset]);
Related