this code must increase the "count" value gradually from 0 to 600. And each time the "count" value changes it must be logged into the console. But instead, I get the "count" value consoled from 6 to 15 times. So the "count" value updates scarcely 20 times instead of desired 600. What might be the problem with this code?
const [count, setCount] = useState(0);
useEffect(() => {
console.log(count);
}, [count]);
const startCount = () => {
for(let i = 0, i < 600, i )
setTimeout(() => {
setCount(prev => prev 1);
}, i);
};
};
CodePudding user response:
- When you set new state component re-renders
- Every time the component re-renders it starts a new loop.
- loop sets a new state, which will cause re-render which will start step-1.
This will cause an infinite loop.
You can achieve a count from 0-n like below.
export default function App() {
const [count, setCount] = React.useState(0);
React.useEffect(() => {
console.log(count);
if (count <= 600) {
setTimeout(() => {
setCount((prev) => prev 1);
}, count);
}
}, [count]);
return (
<div>
<h1>Hello StackBlitz! {count}</h1>
<p>Start editing to see some magic happen :)</p>
</div>
);
}
CodePudding user response:
useState
is operating as intended. See "Batching of state updates"
Your startCount
function is setting things up to update the count every millisecond for the next 600 milliseconds. The state is updating, but probably much faster than you intended (you said "gradually" so I suspect you intended one update per second). And since it's updating faster than your browser is going to be able to re-render your component, the state updates are being batched. Since your console.log
is tied to the rendering cycle via useEffect
, it only runs when your component re-renders.
By the way, setInterval
would be much better to use than setTimeout
here since it's intended for repeatedly calling a function at some regular time interval.
const [isCounting, setCounting] = useState(false)
const startCounting = () => setCounting(true)
const [count, setCount] = useState(0)
// the function within will run when the component mounts,
// and when `isCounting` changes
useEffect(() => {
// don't want to do anything if we're not "counting"
if (isCounting) {
// set up the periodic function call here
const interval = setInterval(() => {
setCount(prev => {
if (prev < 600) {
// "counting" increment
return prev 1
} else {
// turn off the periodic call if we reach 600
clearInterval(interval)
return 600
}
})
}, 500)
// if `isCounting` changes again, react will call the "cleanup" function
// that you return from useEffect, before calling the new useEffect function
return () => clearInterval(interval)
}
}, [isCounting])
CodePudding user response:
Problem is that the for loop
executes 600
times & batches 600
state updates within the next interval
which is i
.
If you increase the setTimeout
interval to 2000 you would see a 2 second delay & then there would be a quick 600 state updates.
const {useState, useEffect} = React;
function App() {
const [count, setCount] = useState(0);
const interval = 2000;
useEffect(() => {
console.log(count);
}, [count]);
const startCount = () => {
for (let i = 0; i < 600; i ) {
setTimeout(() => {
setCount((prev) => prev 1);
}, interval);
}
};
return (
<div className="App">
<h3>{count}</h3><button onClick={startCount}>Start (interval {interval})</button>
</div>
);
}
ReactDOM.render(
<App/>,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react">