I want build a Circular ProgressBar that count at 60 and then automatically stop.
But it can't stop.
I want use React hooks and useEffect
My code here:
https://codesandbox.io/s/nostalgic-khorana-lijdyo?file=/src/App.js:0-686
But the code essence here also:
import React, { useState, useEffect } from "react";
import Circle from "./Circle";
export default function App() {
const [counter, setCounter] = useState(0);
useEffect(() => {
const intervalId = setInterval(() => {
if (counter < 60) {
setCounter((t) => t 1);
console.log(counter);
} else {
console.log(`Why not run?`);
return () => clearInterval(intervalId);
}
return () => clearInterval(intervalId);
}, 100);
}, []);
return (
<div>
<div>
<Circle
size={250}
strokeWidth={5}
percentage={counter}
color="#229880"
/>
</div>
</div>
);
}
CodePudding user response:
Since you do not include counter
in the dependency
of useEffect
, if (counter < 60) {...}
statement will always be true
(since the counter
is equal to 0
in every re-render in react). The easiest way to get the previous value of the counter
would be acquire it in the setCounter
function.
useEffect(() => {
const intervalId = setInterval(() => {
setCounter((t) => {
if (t >= 60) clearInterval(intervalId);
return (t < 60) ? t 1 : t;
});
}, 100);
return () => clearInterval(intervalId);
}, []);
CodePudding user response:
Here is my solution for this
const [counter, setCounter] = useState(0);
const counterValid = counter < 60;
useEffect(() => {
const intervalId = counterValid && setInterval(() =>
setCounter((t) => t 1)
, 100);
return () => clearInterval(intervalId)
}, [counterValid]);
return (
<div>
<div>
<Circle
size={250}
strokeWidth={5}
percentage={counter*(100/60)}
color="#229880"
/>
</div>
</div>
We add counterValid
as a dependency to useEffect
to re-run the effect whenever the validity of the counter changes.
Also note that your circle expects a 1-100 value for percentage
so I multiplied it by 100/60
.
CodePudding user response:
Since you are using counter
within your hook, you should include counter
as a dependency of your useEffect
. In addition, you may need to maintain a state for intervalId
so you could add another state variable for that.
For example:
const [counter, setCounter] = useState(0);
const [intervalId, setIntervalId] = useState(null);
useEffect(() => {
// Only call the setInterval if it is not set already.
if (intervalId) {
return;
}
const newIntervalId = setInterval(() => {
if (counter < 60) {
// You may also be incorrectly setting counter, try just incrementing...
setCounter(counter 1);
console.log(counter);
} else {
console.log(`Why not run?`);
clearInterval(intervalId);
}
}, 100);
setIntervalId(newIntervalId);
// You are also returning the callback to clean up the effect in the wrong place.
// I moved this outside the interval callback for React to have a way to clear the interval during component unmount cycle.
return () => clearInterval(intervalId);
}, [ counter, intervalId ]);
EDIT
There is probably another way to achieve what you are doing, what I introduced may be excessive. The issue may actually be your incrementing logic, I would try just updating that first:
// Change this
setCounter((t) => t 1);
// To this
setCounter(counter 1);