TheNormalPerson
TheNormalPerson

Reputation: 591

Problem with useInterval and react hooks - infinite loop

I have a dice component, and i want it to spit random values n times, and after that show a static value, and this should happen each time props is updated. so i used setInterval in the following code:

  //iter - keep count on how many times we rendered a value
  const [iter, setIter] = useState(0);

  //keep the intervalId given throughout the renderes
  const [intervalId, setIntervalId] = useState(undefined);

  //check each render(iter update) if iter has reached 0, to clear the interval
  useEffect(() => {
    if (iter <= 0) {
      clearInterval(intervalId);
    }
  }, [iter]);

  //if the props updated, call roll dice
  useEffect(() => {
    rollDice();
  }, [props]);

  const rollDice = () => {
    const interval = setInterval(() => {
      //reduce iter every 100ms
      setIter((prev) => prev - 1);
    }, 100);

    //run this interval 10 times
    setIter(10);
    setIntervalId(interval);
  };

This is what the component returns:

 {props.values.map((val, i) => (
    <FontAwesomeIcon
      key={i}
      //randomize icons
      icon={dice[iter ? Math.floor(Math.random() * 5) : val - 1]}
      size={props.size || "4x"}
      color={props.color || "white"}
    />
  ))}

But for some reason i get an infinite loop, the first useEffect keeps firing. Why does this happen and how can i avoid this kind of a bug in the future?

Thank you.

Upvotes: 0

Views: 1184

Answers (1)

HermitCrab
HermitCrab

Reputation: 3274

I think the problem is because you are using state for storing local variables. You call setIter to update iter, but setIter is asynchronous. So it won't update immediately which means that iter might skip 0 and go into negatives numbers which will be infinite if you just check if iter is different than 0. But of course it will work (sort of) if you check that iter is greater than 0.

You should replace your iter state with a ref:

const iter = useRef(0);

Then you can update iter by using its current value:

iter.current = 10;
iter.current--;

Then your icon code will be:

icon={dice[iter.current ? Math.floor(Math.random() * 5) : val - 1]}

Likewise, intervalId should not be stored in state but in a ref:

const intervalId = useRef();

Upvotes: 1

Related Questions