i am a beginner with react and im working on a little clickergame. My problem is, that i want to use useState to automaticly increase the number (with setInterval) but i also want to increase the number with click on the button. The shown percentages are wild hopping because he shows me an early state and a later state at the same time.
function App() {
const [findWorkCount, setfindWorkCount] = useState(0);
setInterval(findWorkRunner, '500');
function findWorkRunner() {
setfindWorkCount(findWorkCount 1);
if (findWorkCount >= 101) {
setfindWorkCount(0);
}
}
return (
<div className="App">
<button
onClick={() => {
setfindWorkCount(findWorkCount 11);
}}
>
Find Job
</button>
<div className="bar">
<div className="fillwork" style={{ width: `${findWorkCount}%` }}>
<div className="counter">{findWorkCount}%</div>
</div>
</div>
</div>
);
}
CodePudding user response:
You can fix the issue by using the callback form of setfindWorkCount in the setInterval to ensure the state update happens after the render:
function App() {
const [findWorkCount, setfindWorkCount] = useState(0);
useEffect(() => {
const intervalId = setInterval(() => {
setfindWorkCount(c => c 1);
if (findWorkCount >= 101) {
setfindWorkCount(0);
}
}, 500);
return () => clearInterval(intervalId);
}, []);
return (
<div className="App">
<button
onClick={() => {
setfindWorkCount(c => c 11);
}}
>
Find Job
</button>
<div className="bar">
<div className="fillwork" style={{ width: `${findWorkCount}%` }}>
<div className="counter">{findWorkCount}%</div>
</div>
</div>
</div>
);
}
CodePudding user response:
Set up a setTimeout
inside useEffect
instead.
useEffect(() => {
const id = setTimeout(() => {
setfindWorkCount(findWorkCount 1);
if (findWorkCount >= 100) setfindWorkCount(0);
}, 500);
return () => clearTimeout(id);
}, [findWorkCount]);
CodePudding user response:
When you use an interval in a function component you need to wrap in a useEffect
block to ensure that it doesn't create an interval on each render. Since findWorkRunner
is a dependency of the useEffect
, you need to wrap it in useCallback
. You should also use findWorkRunner
for the button as well, so the same logic would apply to the button, and the interval updates.
Finally, use a function to update the state, because the updated state is computed using the previous state:
setfindWorkCount(count =>
count inc >= 101 ? 0 : count inc
);
Example:
const { useState, useCallback, useEffect } = React;
function App() {
const [findWorkCount, setfindWorkCount] = useState(0);
const findWorkRunner = useCallback((inc = 1) => {
setfindWorkCount(count =>
count inc >= 101 ? 0 : count inc
);
}, []);
useEffect(() => {
const interval = setInterval(findWorkRunner, '500');
return () => {
clearInterval(interval);
};
}, [findWorkRunner]);
return (
<div className="App">
<button
onClick={() => {
findWorkRunner(11);
}}
>
Find Job
</button>
<div className="bar">
<div className="fillwork" style={{ width: `${findWorkCount}%` }}>
<div className="counter">{findWorkCount}%</div>
</div>
</div>
</div>
);
}
ReactDOM
.createRoot(root)
.render(<App />);
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div id="root"></div>