I was trying to build a simple counter application, the app starts counting ( 1/sec) when I click the start button and stops when I click the stop button.
I came up with 2 different solutions, one using setTimeout and the other using a for loop with delay. both these solutions work for incrementing the number. Are both of these valid react ways for creating a counter? is there a better way to do this?
When stopping the app I can stop it by changing the reference variable halt.current = true
but changing the state setStop(true)
does nothing, why is that?
function App() {
const [get, set] = useState(0);
const [stop, setStop] = useState(false);
const halt = useRef(false);
const loopfn = async () => {
halt.current = false;
setStop(false);
while (true) {
if (halt.current || stop) break;
await new Promise((res) => setTimeout(res, 1000));
set((prev: number) => prev 1);
}
};
const timeoutloopfn = () => {
halt.current = false;
setStop(false);
setTimeout(() => {
if (halt.current || stop) return;
set((prev: number) => prev 1);
timeoutloopfn();
}, 1000);
};
const stoploopref = () => {
halt.current = true;
};
const stoploopst = () => {
setStop((prev: boolean) => true);
};
return (
<div>
<button onClick={loopfn}>for-loop increment</button>
<button onClick={timeoutloopfn}>timeout increment</button>
<button onClick={stoploopref}>stop using ref</button>
<button onClick={stoploopst}>stop using state</button>
<button onClick={() => set(0)}>reset</button>
<p>{get}</p>
</div>
);
}
CodePudding user response:
You may consider using the setInterval
function instead, storing its id in a state and clearing it when stop is set to true:
function App() {
const [get, set] = useState(0);
const [stop, setStop] = useState(false);
const [intervalId, setIntervalId] = useState(-1);
const halt = useRef(false);
useEffect(() => {
// Stop the loop
if (stop && intervalId !== -1) {
clearInterval(intervalId)
setIntervalId(-1)
}
}, [stop])
const timeoutloopfn = () => {
halt.current = false;
setStop(false);
const newIntervalId = setInterval(() => {
set((prev: number) => prev 1);
}, 1000);
setIntervalId(newIntervalId)
};
CodePudding user response:
I would totally recommend useEffect for every function that requires timing. import React, { useRef, useEffect, useState } from "react";
export default function App() {
const [number, setNumber] = useState(100);
let intervalRef = useRef();
const decreaseNum = () => setNumber(prev => prev - 1);
useEffect(() => {
intervalRef.current = setInterval(decreaseNum, 1000);
return () => clearInterval(intervalRef.current);
}, []);
return <div>{number}</div>;
}