useNow and useTickingText
A clock for a surface that says how long something has been running.
import { useNow, useTickingText } from '@basmilius/react-ui';import { useState } from 'react';
import { Button, useNow, useTickingText } from '@basmilius/react-ui';
import { formatClockDuration, formatElapsedShort } from '@basmilius/react-ui/format';
export default function UseNowDemo() {
const [startedAt, setStartedAt] = useState<number | null>(null);
const now = useNow(1000, startedAt !== null);
const [mountedAt] = useState(Date.now);
const ticking = useTickingText(() => formatClockDuration(Date.now() - mountedAt));
return (
<div className="flex flex-col items-center gap-3 text-sm text-text">
<p className="tabular-nums">{startedAt === null ? 'Not running' : `Running for ${formatElapsedShort(now - startedAt)}`}</p>
<Button variant="secondary" onClick={() => setStartedAt(startedAt === null ? Date.now() : null)}>
{startedAt === null ? 'Start' : 'Stop'}
</Button>
<p className="text-xs text-text-muted">
On this page for <span ref={ticking} className="tabular-nums" />
</p>
</div>
);
}useNow
useNow(intervalMs, ticking = true) answers the time in epoch milliseconds and draws the component again every intervalMs. One timer serves the whole surface, and it runs only while ticking is true, so a list with nothing running costs nothing. It starts at the moment the component mounts, so a panel that comes back halfway through does not read zero for a second.
useTickingText
useTickingText(render, intervalMs = 1000) writes the text straight into a node and answers the ref to put on it. Nothing re-renders. Use it for a timer on one line of a long list or a thread, where re-rendering every row each second would cost more than the timer is worth.
const ref = useTickingText(() => formatElapsedShort(Date.now() - startedAt));
return <span ref={ref} />;It writes after every render as well, so the first render already shows the text, and a language or region change lands without waiting for the next tick.