Build a digital clock that shows the current time and ticks every second. It's the canonical "side effect on a timer" component: store the current time in state, start an interval on mount that refreshes it, and clear that interval on unmount. The display is just the time formatted as HH:MM:SS.
// A self-contained component. No props.
function App(): JSX.Element;
An HH:MM:SS display that advances once per second.
9:05:03 -> "09:05:03" (each part zero-padded to two digits)
ticks every second; on unmount the interval is cleared (no leak)
derive from a Date: getHours(), getMinutes(), getSeconds()
Date (or the formatted string); re-render on each tick.setInterval(…, 1000) in an effect with []; return clearInterval.padStart(2, '0') so 9 -> "09".A clock is the textbook timer effect: keep the current time in state, start a one-second interval on mount that updates it, and clear that interval on unmount. Rendering is just formatting the Date as HH:MM:SS.
The component must re-render every second with the new time. React only re-renders when state changes, so you put the time in state and bump it on a timer. The timer is a side effect, so it lives in useEffect — created once on mount and, crucially, torn down on unmount so it doesn't keep firing (a leak). The rest is formatting: pull hours/minutes/seconds off the Date and zero-pad them.
State: now, a Date. An effect with an empty dependency array runs once: it starts setInterval(() => setNow(new Date()), 1000) and returns () => clearInterval(id) for cleanup. Each tick sets a fresh Date, which re-renders. On render, derive hh/mm/ss via getHours/Minutes/Seconds, each padStart(2, '0'), and lay them out with colon separators.
A first attempt mutates a variable or starts the interval in the render body:
let now = new Date();
setInterval(() => { now = new Date(); }, 1000); // not state → no re-render; new interval each render
Reassigning a local doesn't re-render, so the display never updates. And calling setInterval during render creates a new timer on every render (and leaks them all). The fix is state for the value plus a single effect (empty deps) that owns the one interval and cleans it up.
import { useState, useEffect } from 'react';
import './styles.css';
export default function App() {
const [now, setNow] = useState(new Date());
useEffect(() => {
const id = setInterval(() => setNow(new Date()), 1000);
return () => clearInterval(id);
}, []);
const pad = (n: number) => String(n).padStart(2, '0');
const hh = pad(now.getHours());
const mm = pad(now.getMinutes());
const ss = pad(now.getSeconds());
return (
<main className="container">
<h1>Digital Clock</h1>
<div className="clock">
<span className="digit">{hh}</span>
<span className="colon">:</span>
<span className="digit">{mm}</span>
<span className="colon">:</span>
<span className="digit">{ss}</span>
</div>
</main>
);
}
now starts at the current time so the first paint is correct, not 00:00:00. The effect's empty deps mean the interval is created once; its cleanup clearInterval stops it on unmount (and React would re-run setup/cleanup if deps changed, but they don't). Each tick's setNow(new Date()) produces a new Date reference, guaranteeing a re-render. pad zero-fills each part, and the three digit spans plus colons render the display.
Say it mounts at 09:05:03.
now = new Date() ≈ 09:05:03 → hh="09", mm="05", ss="03". Display "09:05:03" immediately (no zero-flash).setInterval starts; React stores the cleanup.setNow(new Date()) ≈ 09:05:04 → re-render → "09:05:04". This repeats every second.ss goes "09" → "10"; pad keeps every part two digits.clearInterval(id) — no more ticks, no leak.useState + setNow each tick.setInterval in render / no []. Stacks a new timer per render and leaks. Fix: one effect with empty deps.return () => clearInterval(id).00:00:00. Starting now at a blank value flashes zeros. Fix: initialize with new Date().9:5:3 looks broken and the layout jumps. Fix: padStart(2, '0').This version keeps the rendered clock parts in one state tuple. Each timer tick reads one fresh Date, formats all three values together, and commits one state update, so the browser output and timing behavior remain identical.
import { useEffect, useState } from 'react';
import './styles.css';
type ClockParts = [string, string, string];
function readClock(date: Date): ClockParts {
const pad = (value: number) => String(value).padStart(2, '0');
return [pad(date.getHours()), pad(date.getMinutes()), pad(date.getSeconds())];
}
export default function App() {
const [parts, setParts] = useState<ClockParts>(() => readClock(new Date()));
useEffect(() => {
const timer = setInterval(() => setParts(readClock(new Date())), 1000);
return () => clearInterval(timer);
}, []);
return (
<main className="container">
<h1>Digital Clock</h1>
<div className="clock">
<span className="digit">{parts[0]}</span>
<span className="colon">:</span>
<span className="digit">{parts[1]}</span>
<span className="colon">:</span>
<span className="digit">{parts[2]}</span>
</div>
</main>
);
}Keep practising the same patterns with a nearby challenge.
No submissions yet
Share your approach and start the discussion.
Build a digital clock that shows the current time and ticks every second. It's the canonical "side effect on a timer" component: store the current time in state, start an interval on mount that refreshes it, and clear that interval on unmount. The display is just the time formatted as HH:MM:SS.
// A self-contained component. No props.
function App(): JSX.Element;
An HH:MM:SS display that advances once per second.
9:05:03 -> "09:05:03" (each part zero-padded to two digits)
ticks every second; on unmount the interval is cleared (no leak)
derive from a Date: getHours(), getMinutes(), getSeconds()
Date (or the formatted string); re-render on each tick.setInterval(…, 1000) in an effect with []; return clearInterval.padStart(2, '0') so 9 -> "09".A clock is the textbook timer effect: keep the current time in state, start a one-second interval on mount that updates it, and clear that interval on unmount. Rendering is just formatting the Date as HH:MM:SS.
The component must re-render every second with the new time. React only re-renders when state changes, so you put the time in state and bump it on a timer. The timer is a side effect, so it lives in useEffect — created once on mount and, crucially, torn down on unmount so it doesn't keep firing (a leak). The rest is formatting: pull hours/minutes/seconds off the Date and zero-pad them.
State: now, a Date. An effect with an empty dependency array runs once: it starts setInterval(() => setNow(new Date()), 1000) and returns () => clearInterval(id) for cleanup. Each tick sets a fresh Date, which re-renders. On render, derive hh/mm/ss via getHours/Minutes/Seconds, each padStart(2, '0'), and lay them out with colon separators.
A first attempt mutates a variable or starts the interval in the render body:
let now = new Date();
setInterval(() => { now = new Date(); }, 1000); // not state → no re-render; new interval each render
Reassigning a local doesn't re-render, so the display never updates. And calling setInterval during render creates a new timer on every render (and leaks them all). The fix is state for the value plus a single effect (empty deps) that owns the one interval and cleans it up.
import { useState, useEffect } from 'react';
import './styles.css';
export default function App() {
const [now, setNow] = useState(new Date());
useEffect(() => {
const id = setInterval(() => setNow(new Date()), 1000);
return () => clearInterval(id);
}, []);
const pad = (n: number) => String(n).padStart(2, '0');
const hh = pad(now.getHours());
const mm = pad(now.getMinutes());
const ss = pad(now.getSeconds());
return (
<main className="container">
<h1>Digital Clock</h1>
<div className="clock">
<span className="digit">{hh}</span>
<span className="colon">:</span>
<span className="digit">{mm}</span>
<span className="colon">:</span>
<span className="digit">{ss}</span>
</div>
</main>
);
}
now starts at the current time so the first paint is correct, not 00:00:00. The effect's empty deps mean the interval is created once; its cleanup clearInterval stops it on unmount (and React would re-run setup/cleanup if deps changed, but they don't). Each tick's setNow(new Date()) produces a new Date reference, guaranteeing a re-render. pad zero-fills each part, and the three digit spans plus colons render the display.
Say it mounts at 09:05:03.
now = new Date() ≈ 09:05:03 → hh="09", mm="05", ss="03". Display "09:05:03" immediately (no zero-flash).setInterval starts; React stores the cleanup.setNow(new Date()) ≈ 09:05:04 → re-render → "09:05:04". This repeats every second.ss goes "09" → "10"; pad keeps every part two digits.clearInterval(id) — no more ticks, no leak.useState + setNow each tick.setInterval in render / no []. Stacks a new timer per render and leaks. Fix: one effect with empty deps.return () => clearInterval(id).00:00:00. Starting now at a blank value flashes zeros. Fix: initialize with new Date().9:5:3 looks broken and the layout jumps. Fix: padStart(2, '0').This version keeps the rendered clock parts in one state tuple. Each timer tick reads one fresh Date, formats all three values together, and commits one state update, so the browser output and timing behavior remain identical.
import { useEffect, useState } from 'react';
import './styles.css';
type ClockParts = [string, string, string];
function readClock(date: Date): ClockParts {
const pad = (value: number) => String(value).padStart(2, '0');
return [pad(date.getHours()), pad(date.getMinutes()), pad(date.getSeconds())];
}
export default function App() {
const [parts, setParts] = useState<ClockParts>(() => readClock(new Date()));
useEffect(() => {
const timer = setInterval(() => setParts(readClock(new Date())), 1000);
return () => clearInterval(timer);
}, []);
return (
<main className="container">
<h1>Digital Clock</h1>
<div className="clock">
<span className="digit">{parts[0]}</span>
<span className="colon">:</span>
<span className="digit">{parts[1]}</span>
<span className="colon">:</span>
<span className="digit">{parts[2]}</span>
</div>
</main>
);
}Keep practising the same patterns with a nearby challenge.
No submissions yet
Share your approach and start the discussion.