Issue
You've a stale enclosure of the state value from the render cycle the timer callback was set from.
Solution
Use a functional state update so the state is updated from the previous state, not the state from the render cycle the update was enqueued in. This allows the interval callback and click handler to seamlessly update state, independently from each other.
setCounter(counter => counter + 1);
In fact, you should use a functional state update anytime the next state value depends on any previous state value. Incrementing a counter is the classic example of using functional updates.
Don't forget to return a cleanup function to clear the interval for when the component umnounts.
Full code
function Counter() {
let [counter, setCounter] = useState(0);
const incCounter = () => {
setCounter((counter) => counter + 1);
};
useEffect(() => {
const timerId = setInterval(() => {
incCounter();
}, 1000);
return () => clearInterval(timerId);
}, []);
return (
<div className="App">
<h1>{counter}</h1>
<button onClick={incCounter}>Inc</button>
</div>
);
}
Demo
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…