My goal is to make a countdown clock with custom inputs. Current it works fine, but the requirement is to make the input fields independent to the timer. That is, currently whatever is entered into the input field, it also changes the timer. The timer should change and start only when start button is clicked. But using onChange in input, changes the timer on the go.
Codesandbox link: https://codesandbox.io/s/lively-grass-6o50xx?file=/src/Timer.js
Code:
const START_DERATION = 10;
function Timer() {
const [currentMinutes, setMinutes] = useState("00");
const [currentSeconds, setSeconds] = useState("00");
const [isStop, setIsStop] = useState(false);
const [duration, setDuration] = useState(START_DERATION);
const [isRunning, setIsRunning] = useState(false);
const startHandler = async () => {
setDuration(
parseInt(currentSeconds, 10) 60 * parseInt(currentMinutes, 10)
);
setIsRunning(true);
};
const stopHandler = () => {
setIsStop(true);
setIsRunning(false);
};
const resetHandler = () => {
setMinutes("00");
setSeconds("00");
setIsRunning(false);
setIsStop(false);
setDuration(START_DERATION);
};
const resumeHandler = () => {
let newDuration =
parseInt(currentMinutes, 10) * 60 parseInt(currentSeconds, 10);
setDuration(newDuration);
setIsRunning(true);
setIsStop(false);
};
useEffect(() => {
if (isRunning === true) {
let timer = duration;
var minutes, seconds;
const interval = setInterval(function () {
if (--timer <= 0) {
resetHandler();
} else {
minutes = parseInt(timer / 60, 10);
seconds = parseInt(timer % 60, 10);
minutes = minutes < 10 ? "0" minutes : minutes;
seconds = seconds < 10 ? "0" seconds : seconds;
setMinutes(minutes);
setSeconds(seconds);
}
}, 1000);
return () => clearInterval(interval);
}
}, [isRunning]);
return (
<div>
<span style={{ display: "flex" }}>
<input
type="number"
onChange={(e) => setMinutes(e.target.value "")}
/>
<p>Minutes</p>
</span>
<span style={{ display: "flex" }}>
<input
type="number"
onChange={(e) => setSeconds(e.target.value "")}
/>
<p>Seconds</p>
</span>
<button onClick={startHandler}>Start</button>
<button
onClick={isStop ? resumeHandler : stopHandler}
disabled={!isRunning && !isStop}
>
Pause/Resume
</button>
<button onClick={resetHandler} disabled={!isRunning && !isStop}>
Reset
</button>
<p>
{currentMinutes}:{currentSeconds}
</p>
</div>
);
}
CodePudding user response:
Instead of making the inputs uncontrolled, you could always conditionally render the timer values. It would look something like:
<p>
{isRunning || isStop
? <>{currentMinutes}:{currentSeconds}</>
: '00:00'
}
</p>
This just checks if the timer is currently running or paused and displays the input values. If it isn't running and isn't paused, it just displays 00:00.
CodePudding user response:
What if you create new state variable disabledInputs and everytime the timer starts, the inputs are disabled so the user can't enter anything? When the timer stops, enable them again.
function Timer() {
const [disabledInputs, setDisabledInputs] = useState(false)
const startHandler = async () => {
setDuration(
parseInt(currentSeconds, 10) 60 * parseInt(currentMinutes, 10)
);
setIsRunning(true);
setDisabledInputs(true)
};
const stopHandler = () => {
// stop timer
setIsStop(true);
setIsRunning(false);
};
const resetHandler = () => {
setMinutes("00");
setSeconds("00");
setIsRunning(false);
setIsStop(false);
setDisabledInputs(false)
setDuration(START_DERATION);
};
const resumeHandler = () => {
let newDuration =
parseInt(currentMinutes, 10) * 60 parseInt(currentSeconds, 10);
setDuration(newDuration);
setIsRunning(true);
setIsStop(false);
};
useEffect(() => {
if (isRunning === true) {
let timer = duration;
var minutes, seconds;
const interval = setInterval(function () {
if (--timer <= 0) {
resetHandler();
} else {
minutes = parseInt(timer / 60, 10);
seconds = parseInt(timer % 60, 10);
minutes = minutes < 10 ? "0" minutes : minutes;
seconds = seconds < 10 ? "0" seconds : seconds;
setMinutes(minutes);
setSeconds(seconds);
}
}, 1000);
return () => clearInterval(interval);
}
}, [isRunning]);
return (
<div>
<span style={{ display: "flex" }}>
<input
disabled={disabledInputs}
type="number"
onChange={(e) => setMinutes(e.target.value "")}
/>
<p>Minutes</p>
</span>
<span style={{ display: "flex" }}>
<input disabled={disabledInputs}
type="number"
onChange={(e) => setSeconds(e.target.value "")}
/>
<p>Seconds</p>
</span>
{/* <button onClick={formatTime}>Format</button> */}
<button onClick={startHandler}>Start</button>
<button
onClick={isStop ? resumeHandler : stopHandler}
disabled={!isRunning && !isStop}
>
Pause/Resume
</button>
<button onClick={resetHandler} disabled={!isRunning && !isStop}>
Reset
</button>
<p>
{isRunning || isStop ? (
<>
{currentMinutes}:{currentSeconds}
</>
) : (
"00:00"
)}
</p>
</div>
);
}
export default Timer;
Actually you can use the same isRunning state to disable them since they change together every time but disabledInputs is more specific and clear.