Reputation: 591
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
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