Created
February 28, 2023 21:36
-
-
Save bjfresh/0c2d1b8b9c38aef115de70d58de64c48 to your computer and use it in GitHub Desktop.
Timer App
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useCallback, FormEvent, useState } from "react"; | |
import { useEffect } from "react"; | |
/* helper functions */ | |
const getUUID = () => window.crypto.randomUUID(); | |
const formatTime = (rawSeconds: number) => { | |
const minutes = Math.floor(rawSeconds / 60); | |
const seconds = rawSeconds % 60; | |
return `${minutes}:${seconds < 10 ? "0" : ""}${seconds}`; | |
}; | |
type Timer = { | |
name: string; | |
startTime: number; | |
}; | |
interface TimerComponentProps { | |
timer: Timer; | |
} | |
const ONE_SECOND = 1000; | |
// This is pretty scrappy, ideally we'd use something robustly tested and off the shelf | |
function useInterval(func: () => void, interval: number | null) { | |
useEffect(() => { | |
if (!interval || interval === 0) { | |
return; | |
} | |
const activeInterval = setInterval(() => func(), interval); | |
return () => clearInterval(activeInterval); | |
}, [func, interval]); | |
} | |
// We output a TimerComponent for each row in the list of Timers | |
function TimerComponent({ timer }: TimerComponentProps) { | |
const [currentTime, setCurrentTime] = useState<number>(timer.startTime); | |
const [isRunning, setIsRunning] = useState<boolean>(true); | |
const handleStart = useCallback(() => { | |
setIsRunning(true); | |
}, []); | |
const handleStop = useCallback(() => { | |
setIsRunning(false); | |
}, []); | |
const handleReset = useCallback(() => { | |
setCurrentTime(0); | |
}, []); | |
useInterval( | |
() => setCurrentTime(currentTime + 1), | |
isRunning ? ONE_SECOND : null | |
); | |
return ( | |
<div | |
style={{ | |
display: "flex", | |
gap: "8px", | |
alignItems: "center", | |
padding: "4px", | |
width: "100%" | |
}} | |
> | |
<div style={{ display: "flex", gap: "8px" }}> | |
{timer.name} {formatTime(currentTime)} | |
</div> | |
<button onClick={handleStart}>Start</button> | |
<button onClick={handleStop}>Stop</button> | |
<button onClick={handleReset}>Reset</button> | |
</div> | |
); | |
} | |
export const App = () => { | |
const [timers, setTimers] = useState<Timer[]>([]); | |
const [currentString, setCurrentString] = useState<string>(""); | |
const handleChange = useCallback( | |
(event: React.ChangeEvent<HTMLInputElement>) => { | |
setCurrentString(event.target.value); | |
}, | |
[] | |
); | |
const handleSubmit = useCallback( | |
(event: FormEvent<HTMLFormElement>) => { | |
event.preventDefault(); | |
const newTimer = { | |
name: currentString, | |
startTime: 0 | |
}; | |
setTimers((prev) => { | |
return [...prev, newTimer]; | |
}); | |
}, | |
[currentString] | |
); | |
return ( | |
<div> | |
<form name="time" onSubmit={handleSubmit}> | |
<label htmlFor="timer" style={{ display: "flex", gap: "8px" }}> | |
<input type="text" name="timer" onChange={handleChange} /> | |
<button type="submit">Add Timer</button> | |
</label> | |
</form> | |
<div> | |
{timers.map((timer: Timer) => { | |
return <TimerComponent key={getUUID()} timer={timer} />; | |
})} | |
</div> | |
</div> | |
); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
For context: the code here is basically functional. One caveat that we’d want to work through is that the useInterval hook doesn’t currently handle state changes + would probably need to be built out much more robustly to maintain a reference so that the timers aren’t lost when the parents re-render. I've tried to keep to a minimal implementation here due to time considerations.