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>
