Runnable lesson · press Try it on the examples to edit and run them here.
A clock needs something React cannot do on its own: time passes. That makes it a perfect first project for effects and cleanup.
Requirements
- Show the current time, updating every second.
- Let the user switch between 24-hour and 12-hour formats.
- Let the user choose a time zone from a short list.
- Clean up the timer when the clock is removed.
- Do not make a screen reader announce every second.
The solution
// A reusable hook: re-renders its component every `ms` milliseconds
function useNow(ms = 1000) {
const [now, setNow] = React.useState(() => new Date());
React.useEffect(() => {
const id = setInterval(() => setNow(new Date()), ms);
return () => clearInterval(id); // stop the timer when the component goes away
}, [ms]);
return now;
}
const ZONES = [
{ value: "UTC", label: "UTC" },
{ value: "Europe/London", label: "London" },
{ value: "Asia/Tokyo", label: "Tokyo" },
{ value: "America/New_York", label: "New York" },
];
function Clock({ timeZone, hour12 }) {
const now = useNow();
const time = new Intl.DateTimeFormat("en-US", {
hour: "2-digit", minute: "2-digit", second: "2-digit", hour12, timeZone,
}).format(now);
const date = new Intl.DateTimeFormat("en-GB", {
weekday: "long", day: "numeric", month: "long", year: "numeric", timeZone,
}).format(now);
return (
<div>
{/* <time> gives the moment a machine-readable value */}
<time dateTime={now.toISOString()} style={{ fontSize: 48, fontVariantNumeric: "tabular-nums", fontWeight: 700 }}>
{time}
</time>
<p style={{ margin: 0 }}>{date}</p>
</div>
);
}
function App() {
const [hour12, setHour12] = React.useState(false);
const [timeZone, setTimeZone] = React.useState("UTC");
const zoneLabel = ZONES.find((z) => z.value === timeZone).label;
return (
<main style={{ fontFamily: "system-ui, sans-serif" }}>
<h1>Digital clock</h1>
<p>Showing the time in {zoneLabel}. Format: {hour12 ? "12-hour" : "24-hour"}</p>
<Clock timeZone={timeZone} hour12={hour12} />
<p>
<label>
Time zone{" "}
<select aria-label="Time zone" value={timeZone} onChange={(e) => setTimeZone(e.target.value)}>
{ZONES.map((z) => <option key={z.value} value={z.value}>{z.label}</option>)}
</select>
</label>{" "}
<button onClick={() => setHour12(!hour12)}>{hour12 ? "Use 24-hour" : "Use 12-hour"}</button>
</p>
</main>
);
}
Walkthrough
useNowis a custom hook (Custom Hooks). It owns the timer and returns the current date. Any component can show a live time with one line.- The effect starts the interval and its cleanup stops it (Cleanup Functions). Without
clearInterval, removing the clock would leave a timer running. setNow(new Date())creates a new value each tick, so React re-renders. The time is state; the formatted string is derived from it.Intl.DateTimeFormatdoes the formatting and the time zone conversion. No manual string building and no extra library (Date Formatting).tabular-numskeeps the digits the same width, so the display does not jitter.- The
<time>element with adateTimeattribute is the correct semantic element for a time value. - Accessibility. The ticking time is not in a live region. Announcing every second would make the page unusable with a screen reader. The surrounding text (“Showing the time in Tokyo. Format: 12-hour”) changes only when the user acts.
Decisions worth noticing
- Why a hook and not the interval inside
Clock? The timer logic is reusable and separate from the display. - Why does the zone and format live in
App? The settings belong to the page;Clockonly displays. Lifting state up keepsClocksimple (Lifting State Up). - Accuracy.
setIntervalcan drift slightly, and browsers slow timers in background tabs. For a display it is fine, because each tick reads the real time fromnew Date()instead of counting seconds itself. That is more reliable thanseconds + 1. - Strict Mode. In development, the effect runs twice, and the cleanup prevents two timers (React Strict Mode).
Make it yours
- Add a second clock to compare two zones side by side; the same
Clockcomponent, two instances. - Add a stopwatch with start, stop and reset using the pattern from useRef.
- Show an analog clock with SVG hands whose rotation comes from the hours, minutes and seconds.
- Pause updates when the page is hidden, using the
visibilitychangeevent.